Compare commits

..

27 Commits

Author SHA1 Message Date
Steve Faulkner
6870bd9b54 Tweaks 2020-07-23 21:53:46 -05:00
Steve Faulkner
8aeff8fb45 Tweaks 2020-07-23 21:52:05 -05:00
Steve Faulkner
ee6f635458 Tweak 2020-07-23 19:03:43 -05:00
Steve Faulkner
ad115a2cce Functional version 2020-07-23 19:02:09 -05:00
Steve Faulkner
0c255a55c8 Fix strict 2020-07-23 18:40:55 -05:00
Steve Faulkner
08e84d93b5 Delete -> destory 2020-07-23 18:20:16 -05:00
Steve Faulkner
e2895b62b4 More updates 2020-07-23 18:18:58 -05:00
Steve Faulkner
155aacdf63 Sanitize usage of delete 2020-07-23 18:17:30 -05:00
Steve Faulkner
f4f2d00d7f More tweaks 2020-07-23 18:12:58 -05:00
Steve Faulkner
f1812077e9 Split up generators 2020-07-23 16:35:05 -05:00
Steve Faulkner
cfe9bd8303 More updates 2020-07-23 11:17:20 -05:00
Steve Faulkner
9db8d11801 Setup Namespaces 2020-07-22 22:40:29 -05:00
Steve Faulkner
769a2e7d1c more tweaks 2020-07-22 21:56:45 -05:00
Steve Faulkner
df544f88b2 First pass at a generated ARM client 2020-07-22 21:37:13 -05:00
Steve Faulkner
acc65c9588 Update README.md 2020-07-22 13:52:47 -05:00
Steve Faulkner
6860d8db1c Remove unused Data Access methods (#107) 2020-07-22 12:03:51 -05:00
Steve Faulkner
3187756d03 Update README.md 2020-07-21 15:03:18 -05:00
Vignesh Rangaishenvi
0f4ff0e49f Support readers (#105) 2020-07-21 11:57:23 -07:00
Steve Faulkner
4f86015be7 Remove OpenActionsStubs (#106) 2020-07-21 13:50:51 -05:00
Steve Faulkner
46cca859e3 Add more files to strict mode (#93) 2020-07-21 12:14:55 -05:00
Steve Faulkner
574fdcaf37 Remove Pane Stubs (#102) 2020-07-21 09:23:51 -05:00
Steve Faulkner
eab6506940 Remove Explorer Stub and ViewModel.Explorer (#101) 2020-07-20 12:59:40 -05:00
Srinath Narayanan
050da28d6e Added support for custom image upload during publish to Gallery (#99)
* Added support for custom image upload

- Dropdown gives an option for URL or image upload
- Preview shows how the card will be displayed in the gallery
- base64 converted image stored in metadata document
- Max limit is 1.5MiB for the image

* fixed lint errors

* addressed PR comments

- Added test

* added snapshot

* fixed failing test
2020-07-17 15:32:39 -07:00
Steve Faulkner
ffae9baca2 Tabs CSS Hotfix and hash CSS file contents (#98)
Co-authored-by: Victor Meng <vimeng@microsoft.com>
2020-07-16 20:35:18 -05:00
Tanuj Mittal
e491c1a042 Use gallery.html instead of /gallery/index.html as endpoint (#94) 2020-07-15 23:41:05 -07:00
Steve Faulkner
444e25c086 More Spark UI Cleanup (#89)
Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
2020-07-15 16:59:04 -05:00
Steve Faulkner
99c6a7ebcc Make explicit any an error (#81)
Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
2020-07-15 16:21:50 -05:00
165 changed files with 11945 additions and 3644 deletions

View File

@@ -1,5 +1,6 @@
**/node_modules/
dist/
Contracts/
src/Api/Apis.ts
src/AuthType.ts
src/Bindings/BindingHandlersRegisterer.ts
@@ -137,7 +138,6 @@ src/Explorer/Panes/AddDatabasePane.test.ts
src/Explorer/Panes/AddDatabasePane.ts
src/Explorer/Panes/BrowseQueriesPane.ts
src/Explorer/Panes/CassandraAddCollectionPane.ts
src/Explorer/Panes/ClusterLibraryPane.ts
src/Explorer/Panes/ContextualPaneBase.ts
src/Explorer/Panes/DeleteCollectionConfirmationPane.test.ts
src/Explorer/Panes/DeleteCollectionConfirmationPane.ts
@@ -145,7 +145,6 @@ src/Explorer/Panes/DeleteDatabaseConfirmationPane.test.ts
src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts
src/Explorer/Panes/ExecuteSprocParamsPane.ts
src/Explorer/Panes/GraphStylingPane.ts
src/Explorer/Panes/LibraryManagePane.ts
src/Explorer/Panes/LoadQueryPane.ts
src/Explorer/Panes/NewVertexPane.ts
src/Explorer/Panes/PaneComponents.ts
@@ -331,10 +330,6 @@ src/Explorer/Controls/Directory/DirectoryListComponent.test.tsx
src/Explorer/Controls/Directory/DirectoryListComponent.tsx
src/Explorer/Controls/Editor/EditorReact.tsx
src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx
src/Explorer/Controls/LibraryManagement/ClusterLibraryGrid.tsx
src/Explorer/Controls/LibraryManagement/ClusterLibraryGridAdapter.tsx
src/Explorer/Controls/LibraryManagement/LibraryManage.tsx
src/Explorer/Controls/LibraryManagement/LibraryManageComponentAdapter.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.tsx

View File

@@ -39,6 +39,7 @@ module.exports = {
curly: "error",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-extraneous-class": "error",
"no-null/no-null": "error"
"no-null/no-null": "error",
"@typescript-eslint/no-explicit-any": "error"
}
};

View File

@@ -1,5 +1,9 @@
# CosmosDB Explorer
UI for Azure Cosmos DB. Powers the [Azure Portal](https://portal.azure.com/), https://cosmos.azure.com/, and the [Cosmos DB Emulator](https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator)
![](https://sdkctlstore.blob.core.windows.net/exe/dataexplorer.gif)
## Getting Started
- `npm install`
@@ -87,6 +91,10 @@ Jest and Puppeteer are used for end to end production runners and are contained
1. Copy .env.example to .env and fill in all variables
2. Run `npm run test:e2e`
### Releasing
We generally adhear to the release strategy [documented by the Azure SDK Guidelines](https://azure.github.io/azure-sdk/policies_repobranching.html#release-branches). Most releases should happen from the master branch. If master contains commits that cannot be released, you may create a release from a `release/` or `hotfix/` branch. See linked documentation for more details.
# Contributing
Please read the [contribution guidelines](./CONTRIBUTING.md).

View File

@@ -2349,9 +2349,9 @@ a:link {
text-decoration: none;
}
.tabsContainer {
.tabsManagerContainer {
height: 100%;
width: 100%;
flex-grow: 1;
overflow: hidden;
}

167
package-lock.json generated
View File

@@ -6024,6 +6024,16 @@
"natural-compare": "^1.4.0",
"pretty-format": "^24.9.0",
"semver": "^6.2.0"
},
"dependencies": {
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
}
}
},
"jest-validate": {
@@ -7577,11 +7587,40 @@
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
"dev": true
},
"@types/mkdirp": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.1.tgz",
"integrity": "sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q==",
"requires": {
"@types/node": "*"
}
},
"@types/node": {
"version": "12.11.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.1.tgz",
"integrity": "sha512-TJtwsqZ39pqcljJpajeoofYRfeZ7/I/OMUQ5pR4q5wOKf2ocrUvBAZUMhWsOvKx3dVc/aaV5GluBivt0sWqA5A=="
},
"@types/node-fetch": {
"version": "2.5.7",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz",
"integrity": "sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==",
"requires": {
"@types/node": "*",
"form-data": "^3.0.0"
},
"dependencies": {
"form-data": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz",
"integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
}
}
},
"@types/normalize-package-data": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
@@ -8403,7 +8442,6 @@
"graceful-fs": "^4.1.11",
"lru-cache": "^4.1.1",
"mississippi": "^2.0.0",
"mkdirp": "^0.5.1",
"move-concurrently": "^1.0.1",
"promise-inflight": "^1.0.1",
"rimraf": "^2.6.2",
@@ -9342,6 +9380,15 @@
"pkg-dir": "^3.0.0"
}
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
},
"pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
@@ -10656,6 +10703,14 @@
"run-queue": "^1.0.0"
},
"dependencies": {
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
},
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@@ -19754,6 +19809,16 @@
"mkdirp": "^0.5.1",
"slash": "^2.0.0",
"source-map": "^0.6.0"
},
"dependencies": {
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
}
}
},
"jest-validate": {
@@ -20337,6 +20402,16 @@
"dev": true,
"optional": true
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"optional": true,
"requires": {
"minimist": "^1.2.5"
}
},
"promise": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
@@ -21287,12 +21362,9 @@
}
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
},
"mkdirp-classic": {
"version": "0.5.3",
@@ -21338,6 +21410,14 @@
"run-queue": "^1.0.3"
},
"dependencies": {
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
},
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@@ -21677,6 +21757,14 @@
"tar": "^4"
},
"dependencies": {
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
},
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@@ -22576,6 +22664,15 @@
"requires": {
"ms": "^2.1.1"
}
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
}
}
},
@@ -25476,6 +25573,16 @@
"mkdirp": "^0.5.0",
"safe-buffer": "^5.1.2",
"yallist": "^3.0.3"
},
"dependencies": {
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
}
}
},
"tar-fs": {
@@ -25833,6 +25940,14 @@
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0="
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
},
"yargs-parser": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz",
@@ -26901,6 +27016,15 @@
"readable-stream": "^2.0.1"
}
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
},
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
@@ -27012,6 +27136,15 @@
"integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==",
"dev": true
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
},
"ws": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
@@ -27224,6 +27357,15 @@
"readable-stream": "^2.0.1"
}
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
},
"readable-stream": {
"version": "2.3.7",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
@@ -27531,6 +27673,17 @@
"dev": true,
"requires": {
"mkdirp": "^0.5.1"
},
"dependencies": {
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
}
}
},
"write-file-atomic": {

View File

@@ -34,6 +34,8 @@
"@nteract/transform-vega": "7.0.6",
"@octokit/rest": "17.9.2",
"@phosphor/widgets": "1.9.3",
"@types/mkdirp": "1.0.1",
"@types/node-fetch": "2.5.7",
"@uifabric/react-cards": "0.109.110",
"@uifabric/styling": "7.13.7",
"abort-controller": "3.0.0",
@@ -60,6 +62,7 @@
"jquery-typeahead": "2.10.6",
"jquery-ui-dist": "1.12.1",
"knockout": "3.5.1",
"mkdirp": "1.0.4",
"monaco-editor": "0.15.6",
"object.entries": "1.1.0",
"office-ui-fabric-react": "7.121.10",
@@ -147,6 +150,7 @@
"less-vars-loader": "1.1.0",
"mini-css-extract-plugin": "0.4.3",
"monaco-editor-webpack-plugin": "1.7.0",
"node-fetch": "2.6.0",
"prettier": "1.19.1",
"puppeteer": "4.0.0",
"raw-loader": "0.5.1",
@@ -187,7 +191,8 @@
"build:contracts": "npm run compile:contracts",
"strictEligibleFiles": "node ./strict-migration-tools/index.js",
"autoAddStrictEligibleFiles": "node ./strict-migration-tools/autoAdd.js",
"compile:fullStrict": "tsc -p ./tsconfig.json --strictNullChecks"
"compile:fullStrict": "tsc -p ./tsconfig.json --strictNullChecks",
"generateARMClients": "ts-node --compiler-options '{\"module\":\"commonjs\"}' utils/armClientGenerator/generator.ts"
},
"repository": {
"type": "git",

View File

@@ -351,6 +351,7 @@ export class HttpStatusCodes {
public static readonly Created: number = 201;
public static readonly Accepted: number = 202;
public static readonly NoContent: number = 204;
public static readonly NotModified: number = 304;
public static readonly Unauthorized: number = 401;
public static readonly Forbidden: number = 403;
public static readonly NotFound: number = 404;

View File

@@ -24,6 +24,7 @@ import { MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { OfferUtils } from "../Utils/OfferUtils";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
export function getCommonQueryOptions(options: FeedOptions): any {
const storedItemPerPageSetting: number = LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage);
@@ -142,7 +143,7 @@ export abstract class DataAccessUtilityBase {
public executeStoredProcedure(
collection: ViewModels.Collection,
storedProcedure: ViewModels.StoredProcedure,
storedProcedure: StoredProcedure,
partitionKeyValue: any,
params: any[]
): Q.Promise<any> {
@@ -615,14 +616,6 @@ export abstract class DataAccessUtilityBase {
}
}
public readSubscription(subscriptionId: string, options: any): Q.Promise<DataModels.Subscription> {
throw new Error("Read subscription not supported on this platform");
}
public readSubscriptionDefaults(subscriptionId: string, quotaId: string, options: any): Q.Promise<string> {
throw new Error("Read subscription defaults not supported on this platform");
}
public queryConflicts(
databaseId: string,
containerId: string,

View File

@@ -12,6 +12,7 @@ import { MessageTypes } from "../Contracts/ExplorerContracts";
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
// TODO: Log all promise resolutions and errors with verbosity levels
export default class DocumentClientUtilityBase {
@@ -164,7 +165,7 @@ export default class DocumentClientUtilityBase {
public executeStoredProcedure(
collection: ViewModels.Collection,
storedProcedure: ViewModels.StoredProcedure,
storedProcedure: StoredProcedure,
partitionKeyValue: any,
params: any[]
): Q.Promise<any> {

View File

@@ -2,6 +2,7 @@ import * as Constants from "../Common/Constants";
import * as ViewModels from "../Contracts/ViewModels";
import { AuthType } from "../AuthType";
import { StringUtils } from "../Utils/StringUtils";
import Explorer from "../Explorer/Explorer";
export default class EnvironmentUtility {
public static getMongoBackendEndpoint(serverId: string, location: string, extensionEndpoint: string = ""): string {
@@ -26,7 +27,7 @@ export default class EnvironmentUtility {
return window.authType === AuthType.AAD;
}
public static getCassandraBackendEndpoint(explorer: ViewModels.Explorer): string {
public static getCassandraBackendEndpoint(explorer: Explorer): string {
const defaultLocation: string = "default";
const location: string = EnvironmentUtility.normalizeRegionName(explorer.databaseAccount().location);
return (

View File

@@ -16,6 +16,7 @@ import { MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
import { MinimalQueryIterator } from "./IteratorUtilities";
const defaultHeaders = {
[HttpHeaders.apiType]: ApiType.MongoDB.toString(),
@@ -23,7 +24,7 @@ const defaultHeaders = {
[CosmosSDKConstants.HttpHeaders.Version]: "2017-11-15"
};
function authHeaders(): any {
function authHeaders() {
if (window.authType === AuthType.EncryptedToken) {
return { [HttpHeaders.guestAccessToken]: CosmosClient.accessToken() };
} else {
@@ -31,21 +32,21 @@ function authHeaders(): any {
}
}
export function queryIterator(databaseId: string, collection: Collection, query: string): any {
export function queryIterator(databaseId: string, collection: Collection, query: string): MinimalQueryIterator {
let continuationToken: string;
return {
fetchNext: () => {
return queryDocuments(databaseId, collection, false, query).then(response => {
continuationToken = response.continuationToken;
const headers = {} as any;
response.headers.forEach((value: any, key: any) => {
const headers: { [key: string]: string | number } = {};
response.headers.forEach((value, key) => {
headers[key] = value;
});
return {
resources: response.documents,
headers,
requestCharge: headers[CosmosSDKConstants.HttpHeaders.RequestCharge],
activityId: headers[CosmosSDKConstants.HttpHeaders.ActivityId],
requestCharge: Number(headers[CosmosSDKConstants.HttpHeaders.RequestCharge]),
activityId: String(headers[CosmosSDKConstants.HttpHeaders.ActivityId]),
hasMoreResults: !!continuationToken
};
});
@@ -114,7 +115,8 @@ export function queryDocuments(
headers: response.headers
};
}
return errorHandling(response, "querying documents", params);
errorHandling(response, "querying documents", params);
return undefined;
});
}
@@ -165,7 +167,7 @@ export function createDocument(
databaseId: string,
collection: Collection,
partitionKeyProperty: string,
documentContent: any
documentContent: unknown
): Promise<DataModels.DocumentId> {
const databaseAccount = CosmosClient.databaseAccount();
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
@@ -204,7 +206,7 @@ export function updateDocument(
databaseId: string,
collection: Collection,
documentId: ViewModels.DocumentId,
documentContent: any
documentContent: unknown
): Promise<DataModels.DocumentId> {
const databaseAccount = CosmosClient.databaseAccount();
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
@@ -228,7 +230,7 @@ export function updateDocument(
return window
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
method: "PUT",
body: documentContent,
body: JSON.stringify(documentContent),
headers: {
...defaultHeaders,
...authHeaders(),
@@ -248,7 +250,7 @@ export function deleteDocument(
databaseId: string,
collection: Collection,
documentId: ViewModels.DocumentId
): Promise<any> {
): Promise<void> {
const databaseAccount = CosmosClient.databaseAccount();
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/");
@@ -295,7 +297,7 @@ export function createMongoCollectionWithProxy(
sharedThroughput: boolean,
isSharded: boolean,
autopilotOptions?: DataModels.RpOptions
): Promise<any> {
): Promise<DataModels.Collection> {
const databaseAccount = CosmosClient.databaseAccount();
const params: DataModels.MongoParameters = {
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
@@ -335,7 +337,7 @@ export function createMongoCollectionWithProxy(
)
.then(response => {
if (response.ok) {
return undefined;
return response.json();
}
return errorHandling(response, "creating collection", params);
});
@@ -352,7 +354,7 @@ export function createMongoCollectionWithARM(
sharedThroughput: boolean,
isSharded: boolean,
additionalOptions?: DataModels.RpOptions
): Promise<any> {
): Promise<DataModels.CreateCollectionWithRpResponse> {
const databaseAccount = CosmosClient.databaseAccount();
const params: DataModels.MongoParameters = {
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
@@ -396,7 +398,9 @@ export function getEndpoint(databaseAccount: ViewModels.DatabaseAccount): string
return url;
}
async function errorHandling(response: any, action: string, params: any): Promise<any> {
// TODO: This function throws most of the time except on Forbidden which is a bit strange
// It causes problems for TypeScript understanding the types
async function errorHandling(response: Response, action: string, params: unknown): Promise<void> {
const errorMessage = await response.text();
// Log the error where the user can see it
NotificationConsoleUtils.logConsoleMessage(
@@ -420,7 +424,7 @@ export async function _createMongoCollectionWithARM(
armEndpoint: string,
params: DataModels.MongoParameters,
rpOptions: DataModels.RpOptions
): Promise<any> {
): Promise<DataModels.CreateCollectionWithRpResponse> {
const rpPayloadToCreateCollection: DataModels.MongoCreationRequest = {
properties: {
resource: {
@@ -448,12 +452,13 @@ export async function _createMongoCollectionWithARM(
}
try {
await new ResourceProviderClient(armEndpoint).putAsync(
return new ResourceProviderClient<DataModels.CreateCollectionWithRpResponse>(armEndpoint).putAsync(
getARMCreateCollectionEndpoint(params),
DataExplorerConstants.ArmApiVersions.publicVersion,
rpPayloadToCreateCollection
);
} catch (response) {
return errorHandling(response, "creating collection", undefined);
errorHandling(response, "creating collection", undefined);
return undefined;
}
}

View File

@@ -10,6 +10,7 @@ import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import * as Logger from "./Logger";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import { QueryUtils } from "../Utils/QueryUtils";
import Explorer from "../Explorer/Explorer";
export class QueriesClient implements ViewModels.QueriesClient {
private static readonly PartitionKey: DataModels.PartitionKey = {
@@ -20,7 +21,7 @@ export class QueriesClient implements ViewModels.QueriesClient {
private static readonly FetchQuery: string = "SELECT * FROM c";
private static readonly FetchMongoQuery: string = "{}";
public constructor(private container: ViewModels.Explorer) {}
public constructor(private container: Explorer) {}
public async setupQueriesCollection(): Promise<DataModels.Collection> {
const queriesCollection: ViewModels.Collection = this.findQueriesCollection();

View File

@@ -437,10 +437,8 @@ export interface Tenant {
export interface AccountKeys {
primaryMasterKey: string;
secondaryMasterKey: string;
properties: {
primaryReadonlyMasterKey: string;
secondaryReadonlyMasterKey: string;
};
primaryReadonlyMasterKey: string;
secondaryReadonlyMasterKey: string;
}
export interface AfecFeature {

View File

@@ -1,32 +1,21 @@
import * as DataModels from "./DataModels";
import * as Entities from "../Explorer/Tables/Entities";
import * as monaco from "monaco-editor";
import DocumentClientUtilityBase from "../Common/DocumentClientUtilityBase";
import Q from "q";
import QueryViewModel from "../Explorer/Tables/QueryBuilder/QueryViewModel";
import TableEntityListViewModel from "../Explorer/Tables/DataTable/TableEntityListViewModel";
import { AccessibleVerticalList } from "../Explorer/Tree/AccessibleVerticalList";
import { ArcadiaWorkspaceItem } from "../Explorer/Controls/Arcadia/ArcadiaMenuPicker";
import { CassandraTableKey, CassandraTableKeys, TableDataClient } from "../Explorer/Tables/TableDataClient";
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { ExecuteSprocParam } from "../Explorer/Panes/ExecuteSprocParamsPane";
import { GitHubClient } from "../GitHub/GitHubClient";
import { IColumnSetting } from "../Explorer/Panes/Tables/TableColumnOptionsPane";
import { JunoClient, IGalleryItem } from "../Juno/JunoClient";
import { Library } from "./DataModels";
import { MostRecentActivity } from "../Explorer/MostRecentActivity/MostRecentActivity";
import { NotebookContentItem } from "../Explorer/Notebook/NotebookContentItem";
import { PlatformType } from "../PlatformType";
import { QueryMetrics } from "@azure/cosmos";
import { SetupNotebooksPane } from "../Explorer/Panes/SetupNotebooksPane";
import { Splitter } from "../Common/Splitter";
import { StringInputPane } from "../Explorer/Panes/StringInputPane";
import { TabsManager } from "../Explorer/Tabs/TabsManager";
import { TextFieldProps } from "../Explorer/Controls/DialogReactComponent/DialogComponent";
import { UploadDetails } from "../workers/upload/definitions";
import { UploadItemsPaneAdapter } from "../Explorer/Panes/UploadItemsPaneAdapter";
import { ReactAdapter } from "../Bindings/ReactBindingHandler";
import Explorer from "../Explorer/Explorer";
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
import ConflictsTab from "../Explorer/Tabs/ConflictsTab";
import Trigger from "../Explorer/Tree/Trigger";
export interface ExplorerOptions {
documentClientUtility: DocumentClientUtilityBase;
@@ -42,266 +31,12 @@ export interface NavbarButtonConfig extends CommandButtonComponentProps {}
export interface DatabaseAccount extends DataModels.DatabaseAccount {}
export interface Explorer {
flight: ko.Observable<string>;
handleMessage(event: MessageEvent): void;
isRefreshingExplorer: ko.Observable<boolean>;
databaseAccount: ko.Observable<DatabaseAccount>;
subscriptionType: ko.Observable<SubscriptionType>;
quotaId: ko.Observable<string>;
hasWriteAccess: ko.Observable<boolean>;
defaultExperience: ko.Observable<string>;
isPreferredApiDocumentDB: ko.Computed<boolean>;
isPreferredApiCassandra: ko.Computed<boolean>;
isPreferredApiTable: ko.Computed<boolean>;
isPreferredApiGraph: ko.Computed<boolean>;
isPreferredApiMongoDB: ko.Computed<boolean>;
isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
isDatabaseNodeOrNoneSelected(): boolean;
isDatabaseNodeSelected(): boolean;
isNodeKindSelected(nodeKind: string): boolean;
isNoneSelected(): boolean;
isSelectedDatabaseShared(): boolean;
deleteDatabaseText: ko.Observable<string>;
deleteCollectionText: ko.Subscribable<string>; // Our code assigns to a ko.Observable, but unit test assigns to ko.Computed
addCollectionText: ko.Observable<string>;
addDatabaseText: ko.Observable<string>;
collectionTitle: ko.Observable<string>;
collectionTreeNodeAltText: ko.Observable<string>;
refreshTreeTitle: ko.Observable<string>;
isAccountReady: ko.Observable<boolean>;
collectionCreationDefaults: CollectionCreationDefaults;
isEmulator: boolean;
features: ko.Observable<any>;
serverId: ko.Observable<string>;
extensionEndpoint: ko.Observable<string>;
armEndpoint: ko.Observable<string>;
isFeatureEnabled: (feature: string) => boolean;
isGalleryPublishEnabled: ko.Computed<boolean>;
isGitHubPaneEnabled: ko.Observable<boolean>;
isPublishNotebookPaneEnabled: ko.Observable<boolean>;
isRightPanelV2Enabled: ko.Computed<boolean>;
canExceedMaximumValue: ko.Computed<boolean>;
hasAutoPilotV2FeatureFlag: ko.Computed<boolean>;
isHostedDataExplorerEnabled: ko.Computed<boolean>;
isNotificationConsoleExpanded: ko.Observable<boolean>;
isTryCosmosDBSubscription: ko.Observable<boolean>;
canSaveQueries: ko.Computed<boolean>;
parentFrameDataExplorerVersion: ko.Observable<string>;
documentClientUtility: DocumentClientUtilityBase;
notificationsClient: NotificationsClient;
queriesClient: QueriesClient;
tableDataClient: TableDataClient;
splitter: Splitter;
notificationConsoleData: ko.ObservableArray<ConsoleData>;
// Selection
selectedNode: ko.Observable<TreeNode>;
// Tree
databases: ko.ObservableArray<Database>;
nonSystemDatabases: ko.Computed<Database[]>;
selectedDatabaseId: ko.Computed<string>;
selectedCollectionId: ko.Computed<string>;
isLeftPaneExpanded: ko.Observable<boolean>;
// Resource Token
resourceTokenDatabaseId: ko.Observable<string>;
resourceTokenCollectionId: ko.Observable<string>;
resourceTokenCollection: ko.Observable<CollectionBase>;
resourceTokenPartitionKey: ko.Observable<string>;
isAuthWithResourceToken: ko.Observable<boolean>;
isResourceTokenCollectionNodeSelected: ko.Computed<boolean>;
// Tabs
isTabsContentExpanded: ko.Observable<boolean>;
tabsManager: TabsManager;
// Contextual Panes
addDatabasePane: AddDatabasePane;
addCollectionPane: AddCollectionPane;
deleteCollectionConfirmationPane: DeleteCollectionConfirmationPane;
deleteDatabaseConfirmationPane: DeleteDatabaseConfirmationPane;
graphStylingPane: GraphStylingPane;
addTableEntityPane: AddTableEntityPane;
editTableEntityPane: EditTableEntityPane;
tableColumnOptionsPane: TableColumnOptionsPane;
querySelectPane: QuerySelectPane;
newVertexPane: NewVertexPane;
cassandraAddCollectionPane: CassandraAddCollectionPane;
settingsPane: SettingsPane;
executeSprocParamsPane: ExecuteSprocParamsPane;
renewAdHocAccessPane: RenewAdHocAccessPane;
uploadItemsPane: UploadItemsPane;
uploadItemsPaneAdapter: UploadItemsPaneAdapter;
loadQueryPane: LoadQueryPane;
saveQueryPane: ContextualPane;
browseQueriesPane: BrowseQueriesPane;
uploadFilePane: UploadFilePane;
stringInputPane: StringInputPane;
setupNotebooksPane: SetupNotebooksPane;
libraryManagePane: ContextualPane;
clusterLibraryPane: ContextualPane;
gitHubReposPane: ContextualPane;
publishNotebookPaneAdapter: ReactAdapter;
// Facade
logConsoleData(data: ConsoleData): void;
isNodeKindSelected(nodeKind: string): boolean;
initDataExplorerWithFrameInputs(inputs: DataExplorerInputsFrame): Q.Promise<void>;
toggleLeftPaneExpanded(): void;
refreshDatabaseForResourceToken(): Q.Promise<void>;
refreshAllDatabases(isInitialLoad?: boolean): Q.Promise<any>;
closeAllPanes(): void;
findSelectedDatabase(): Database;
findDatabaseWithId(databaseRid: string): Database;
isLastDatabase(): boolean;
isLastNonEmptyDatabase(): boolean;
findSelectedCollection(): Collection;
isLastCollection(): boolean;
findSelectedStoredProcedure(): StoredProcedure;
findSelectedUDF(): UserDefinedFunction;
findSelectedTrigger(): Trigger;
findCollection(rid: string): Collection;
provideFeedbackEmail(): void;
expandConsole: () => void;
collapseConsole: () => void;
generateSharedAccessData(): void;
getPlatformType(): PlatformType;
isConnectExplorerVisible(): boolean;
isRunningOnNationalCloud(): boolean;
displayConnectExplorerForm(): void;
hideConnectExplorerForm(): void;
displayContextSwitchPromptForConnectionString(connectionString: string): void;
displayGuestAccessTokenRenewalPrompt(): void;
rebindDocumentClientUtility(documentClientUtility: DocumentClientUtilityBase): void;
renewExplorerShareAccess: (explorer: Explorer, token: string) => Q.Promise<void>;
renewShareAccess(accessInput: string): Q.Promise<void>;
onUpdateTabsButtons: (buttons: NavbarButtonConfig[]) => void;
onNewCollectionClicked: () => void;
showOkModalDialog: (title: string, msg: string) => void;
showOkCancelModalDialog: (
title: string,
msg: string,
okLabel: string,
onOk: () => void,
cancelLabel: string,
onCancel: () => void
) => void;
showOkCancelTextFieldModalDialog: (
title: string,
msg: string,
okLabel: string,
onOk: () => void,
cancelLabel: string,
onCancel: () => void,
textFiledProps: TextFieldProps,
isPrimaryButtonDisabled?: boolean
) => void;
// Analytics
isNotebookEnabled: ko.Observable<boolean>;
isSparkEnabled: ko.Observable<boolean>;
isNotebooksEnabledForAccount: ko.Observable<boolean>;
isSparkEnabledForAccount: ko.Observable<boolean>;
hasStorageAnalyticsAfecFeature: ko.Observable<boolean>;
openEnableSynapseLinkDialog(): void;
isSynapseLinkUpdating: ko.Observable<boolean>;
notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>;
sparkClusterConnectionInfo: ko.Observable<DataModels.SparkClusterConnectionInfo>;
arcadiaToken: ko.Observable<string>;
arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>;
memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
notebookManager?: any; // This is dynamically loaded
openNotebook(notebookContentItem: NotebookContentItem): Promise<boolean>; // True if it was opened, false otherwise
resetNotebookWorkspace(): void;
importAndOpen: (path: string) => Promise<boolean>;
importAndOpenContent: (name: string, content: string) => Promise<boolean>;
publishNotebook: (name: string, content: string) => void;
openNotebookTerminal: (kind: TerminalKind) => void;
openGallery: (notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean) => void;
openNotebookViewer: (notebookUrl: string) => void;
notebookWorkspaceManager: NotebookWorkspaceManager;
sparkClusterManager: SparkClusterManager;
mostRecentActivity: MostRecentActivity;
initNotebooks: (databaseAccount: DataModels.DatabaseAccount) => Promise<void>;
deleteCluster(): void;
openSparkMasterTab(): Promise<void>;
handleOpenFileAction(path: string): Promise<void>;
// Notebook operations
openNotebook(notebookContentItem: NotebookContentItem): Promise<boolean>; // True if it was opened, false otherwise
deleteNotebookFile: (item: NotebookContentItem) => Promise<void>;
onCreateDirectory(parent: NotebookContentItem): Q.Promise<NotebookContentItem>;
onNewNotebookClicked: (parent?: NotebookContentItem) => void;
onUploadToNotebookServerClicked: (parent?: NotebookContentItem) => void;
renameNotebook: (notebookFile: NotebookContentItem) => Q.Promise<NotebookContentItem>;
readFile: (notebookFile: NotebookContentItem) => Promise<string>;
downloadFile: (notebookFile: NotebookContentItem) => Promise<void>;
createNotebookContentItemFile: (name: string, filepath: string) => NotebookContentItem;
refreshContentItem(item: NotebookContentItem): Promise<void>;
getNotebookBasePath(): string;
createWorkspace(): Promise<string>;
createSparkPool(workspaceId: string): Promise<string>;
}
export interface NotebookWorkspaceManager {
getNotebookWorkspacesAsync(cosmosAccountResourceId: string): Promise<DataModels.NotebookWorkspace[]>;
getNotebookWorkspaceAsync(
cosmosAccountResourceId: string,
notebookWorkspaceId: string
): Promise<DataModels.NotebookWorkspace>;
createNotebookWorkspaceAsync(cosmosdbResourceId: string, notebookWorkspaceId: string): Promise<void>;
deleteNotebookWorkspaceAsync(cosmosdbResourceId: string, notebookWorkspaceId: string): Promise<void>;
getNotebookConnectionInfoAsync(
cosmosAccountResourceId: string,
notebookWorkspaceId: string
): Promise<DataModels.NotebookWorkspaceConnectionInfo>;
startNotebookWorkspaceAsync(cosmosdbResourceId: string, notebookWorkspaceId: string): Promise<void>;
}
export interface KernelConnectionMetadata {
name: string;
configurationEndpoints: DataModels.NotebookConfigurationEndpoints;
notebookConnectionInfo: DataModels.NotebookWorkspaceConnectionInfo;
}
export interface SparkClusterManager {
getClustersAsync(cosmosAccountResourceId: string): Promise<DataModels.SparkCluster[]>;
getClusterAsync(cosmosAccountResourceId: string, clusterId: string): Promise<DataModels.SparkCluster>;
createClusterAsync(cosmosAccountResourceId: string, cluster: Partial<DataModels.SparkCluster>): Promise<void>;
updateClusterAsync(
cosmosAccountResourceId: string,
clusterId: string,
sparkCluster: DataModels.SparkCluster
): Promise<DataModels.SparkCluster>;
deleteClusterAsync(cosmosAccountResourceId: string, clusterId: string): Promise<void>;
getClusterConnectionInfoAsync(
cosmosAccountResourceId: string,
clusterId: string
): Promise<DataModels.SparkClusterConnectionInfo>;
getLibrariesAsync(cosmosdbResourceId: string): Promise<Library[]>;
getLibraryAsync(cosmosdbResourceId: string, libraryName: string): Promise<Library>;
addLibraryAsync(cosmosdbResourceId: string, libraryName: string, library: Library): Promise<void>;
deleteLibraryAsync(cosmosdbResourceId: string, libraryName: string): Promise<void>;
}
export interface ArcadiaResourceManager {
getWorkspacesAsync(arcadiaResourceId: string): Promise<DataModels.ArcadiaWorkspace[]>;
getWorkspaceAsync(arcadiaResourceId: string, workspaceId: string): Promise<DataModels.ArcadiaWorkspace>;
listWorkspacesAsync(subscriptionIds: string[]): Promise<DataModels.ArcadiaWorkspace[]>;
listSparkPoolsAsync(resourceId: string): Promise<DataModels.SparkPool[]>;
}
export interface TokenProvider {
getAuthHeader(): Promise<Headers>;
}
@@ -505,48 +240,6 @@ export interface ConflictId {
loadConflict(): Q.Promise<any>;
}
export interface StoredProcedure extends TreeNode {
container: Explorer;
collection: Collection;
rid: string;
self: string;
id: ko.Observable<string>;
body: ko.Observable<string>;
delete(): void;
open: () => void;
select(): void;
execute(params: string[], partitionKeyValue?: string): void;
}
export interface UserDefinedFunction extends TreeNode {
container: Explorer;
collection: Collection;
rid: string;
self: string;
id: ko.Observable<string>;
body: ko.Observable<string>;
delete(): void;
open: () => void;
select(): void;
}
export interface Trigger extends TreeNode {
container: Explorer;
collection: Collection;
rid: string;
self: string;
id: ko.Observable<string>;
body: ko.Observable<string>;
triggerType: ko.Observable<string>;
triggerOperation: ko.Observable<string>;
delete(): void;
open: () => void;
select(): void;
}
/**
* Options used to initialize pane
*/
@@ -598,78 +291,6 @@ export interface AddCollectionPaneOptions extends PaneOptions {
databaseSelfLink?: string;
}
export interface AddCollectionPane extends ContextualPane {
collectionIdTitle: ko.Observable<string>;
collectionWithThroughputInSharedTitle: ko.Observable<string>;
databaseId: ko.Observable<string>;
partitionKey: ko.Observable<string>;
storage: ko.Observable<string>;
throughputSinglePartition: ko.Observable<number>;
throughputMultiPartition: ko.Observable<number>;
open: (databaseId?: string) => void;
onStorageOptionsKeyDown(source: any, event: KeyboardEvent): boolean;
onRupmOptionsKeyDown(source: any, event: KeyboardEvent): void;
onEnableSynapseLinkButtonClicked: () => void;
}
export interface AddDatabasePane extends ContextualPane {}
export interface DeleteDatabaseConfirmationPane extends ContextualPane {
databaseIdConfirmation: ko.Observable<string>;
databaseIdConfirmationText: ko.Observable<string>;
databaseDeleteFeedback: ko.Observable<string>;
}
export interface DeleteCollectionConfirmationPane extends ContextualPane {
collectionIdConfirmation: ko.Observable<string>;
collectionIdConfirmationText: ko.Observable<string>;
containerDeleteFeedback: ko.Observable<string>;
recordDeleteFeedback: ko.Observable<boolean>;
}
export interface SettingsPane extends ContextualPane {
pageOption: ko.Observable<string>;
customItemPerPage: ko.Observable<number>;
crossPartitionQueryEnabled: ko.Observable<boolean>;
maxDegreeOfParallelism: ko.Observable<number>;
shouldShowQueryPageOptions: ko.Computed<boolean>;
onCustomPageOptionsKeyDown(source: any, event: KeyboardEvent): boolean;
onUnlimitedPageOptionKeyDown(source: any, event: KeyboardEvent): boolean;
onJsonDisplayResultsKeyDown(source: any, event: KeyboardEvent): boolean;
onGraphDisplayResultsKeyDown(source: any, event: KeyboardEvent): boolean;
}
export interface ExecuteSprocParamsPane extends ContextualPane {
params: ko.ObservableArray<ExecuteSprocParam>;
partitionKeyValue: ko.Observable<string>;
addNewParam(): void;
}
export interface RenewAdHocAccessPane extends ContextualPane {
accessKey: ko.Observable<string>;
}
export interface UploadItemsPane extends ContextualPane {
selectedFilesTitle: ko.Observable<string>;
files: ko.Observable<FileList>;
updateSelectedFiles(element: any, event: any): void;
}
export interface LoadQueryPane extends ContextualPane {
selectedFilesTitle: ko.Observable<string>;
files: ko.Observable<FileList>;
loadQueryFromFile(file: File): Q.Promise<void>;
}
export interface BrowseQueriesPane extends ContextualPane {
loadSavedQuery: (savedQuery: DataModels.Query) => void;
}
export interface UploadFilePaneOpenOptions {
paneTitle: string;
selectFileInputLabel: string;
@@ -692,10 +313,6 @@ export interface StringInputPaneOpenOptions {
defaultInput?: string;
}
export interface UploadFilePane extends ContextualPane {
openWithOptions: (options: UploadFilePaneOpenOptions) => void;
}
/**
* Graph configuration
*/
@@ -740,41 +357,6 @@ export interface InputProperty {
values: InputPropertyValue[];
}
export interface GraphStylingPane extends ContextualPane {
setData(graphConfigUIData: GraphConfigUiData): void;
}
export interface NewVertexPane extends ContextualPane {
setPartitionKeyProperty: (pKeyProp: string) => void;
subscribeOnSubmitCreate: (callback: (newVertexData: NewVertexData) => void) => void;
}
export interface AddTableEntityPane extends ContextualPane {
tableViewModel: TableEntityListViewModel;
}
export interface EditTableEntityPane extends ContextualPane {
originEntity: Entities.ITableEntity;
tableViewModel: TableEntityListViewModel;
originalNumberOfProperties: number;
}
export interface TableColumnOptionsPane extends ContextualPane {
tableViewModel: TableEntityListViewModel;
parameters: IColumnSetting;
setDisplayedColumns(columnNames: string[], order: number[], visible: boolean[]): void;
}
export interface QuerySelectPane extends ContextualPane {
queryViewModel: QueryViewModel;
}
export interface CassandraAddCollectionPane extends ContextualPane {
createTableQuery: ko.Observable<string>;
keyspaceId: ko.Observable<string>;
userTableQuery: ko.Observable<string>;
}
export interface Editable<T> extends ko.Observable<T> {
setBaseline(baseline: T): void;
@@ -981,111 +563,6 @@ export interface DocumentsTab extends Tab {
loadNextPage(): Q.Promise<any>;
}
export interface ConflictsTab extends Tab {
/* Conflicts Grid */
selectedConflictId: ko.Observable<ConflictId>;
selectedConflictContent: Editable<any>;
selectedConflictCurrent: Editable<any>;
onConflictIdClick(conflictId: ConflictId): Q.Promise<any>;
dataContentsGridScrollHeight: ko.Observable<string>;
accessibleDocumentList: AccessibleVerticalList;
documentContentsGridId: string;
partitionKey: DataModels.PartitionKey;
partitionKeyPropertyHeader: string;
partitionKeyProperty: string;
conflictIds: ko.ObservableArray<ConflictId>;
/* Document Editor */
isEditorDirty: ko.Computed<boolean>;
editorState: ko.Observable<DocumentExplorerState>;
onValidDocumentEdit(content: any): Q.Promise<any>;
onInvalidDocumentEdit(content: any): Q.Promise<any>;
loadingConflictData: ko.Observable<boolean>;
onAcceptChangesClick(): Q.Promise<any>;
onDiscardClick(): Q.Promise<any>;
initDocumentEditorForCreate(documentId: ConflictId, documentToInsert: any): Q.Promise<any>;
initDocumentEditorForReplace(documentId: ConflictId, conflictContent: any, currentContent: any): Q.Promise<any>;
initDocumentEditorForDelete(documentId: ConflictId, documentToDelete: any): Q.Promise<any>;
initDocumentEditorForNoOp(conflictId: ConflictId): Q.Promise<any>;
loadNextPage(): Q.Promise<any>;
}
export interface SettingsTab extends Tab {
/*state*/
throughput: ko.Observable<number>;
timeToLive: ko.Observable<string>;
timeToLiveSeconds: ko.Observable<number>;
geospatialVisible: ko.Computed<boolean>;
geospatialConfigType: ko.Observable<string>;
indexingPolicyContent: ko.Observable<DataModels.IndexingPolicy>;
rupm: ko.Observable<string>;
requestUnitsUsageCost: ko.Computed<string>;
canThroughputExceedMaximumValue: ko.Computed<boolean>;
shouldDisplayPortalUsePrompt: ko.Computed<boolean>;
warningMessage: ko.Computed<string>;
ttlOffFocused: ko.Observable<boolean>;
ttlOnDefaultFocused: ko.Observable<boolean>;
ttlOnFocused: ko.Observable<boolean>;
indexingPolicyElementFocused: ko.Observable<boolean>;
notificationStatusInfo: ko.Observable<string>;
shouldShowNotificationStatusPrompt: ko.Computed<boolean>;
shouldShowStatusBar: ko.Computed<boolean>;
pendingNotification: ko.Observable<DataModels.Notification>;
conflictResolutionPolicyMode: ko.Observable<string>;
conflictResolutionPolicyPath: ko.Observable<string>;
conflictResolutionPolicyProcedure: ko.Observable<string>;
rupmVisible: ko.Computed<boolean>;
costsVisible: ko.Computed<boolean>;
minRUAnotationVisible: ko.Computed<boolean>;
/* Command Bar */
saveSettingsButton: Button;
discardSettingsChangesButton: Button;
onSaveClick(): Q.Promise<any>;
onRevertClick(): Q.Promise<any>;
/* Indexing Policy Editor */
isIndexingPolicyEditorInitializing: ko.Observable<boolean>;
indexingPolicyEditor: ko.Observable<monaco.editor.IStandaloneCodeEditor>;
onValidIndexingPolicyEdit(content: any): Q.Promise<any>;
onInvalidIndexingPolicyEdit(content: any): Q.Promise<any>;
onSaveClick(): Q.Promise<any>;
onRevertClick(): Q.Promise<any>;
}
export interface DatabaseSettingsTab extends Tab {
/*state*/
throughput: ko.Observable<number>;
requestUnitsUsageCost: ko.PureComputed<string>;
canThroughputExceedMaximumValue: ko.Computed<boolean>;
warningMessage: ko.Computed<string>;
notificationStatusInfo: ko.Observable<string>;
shouldShowNotificationStatusPrompt: ko.Computed<boolean>;
shouldShowStatusBar: ko.Computed<boolean>;
pendingNotification: ko.Observable<DataModels.Notification>;
costsVisible: ko.Computed<boolean>;
minRUAnotationVisible: ko.Computed<boolean>;
/* Command Bar */
saveSettingsButton: Button;
discardSettingsChangesButton: Button;
onSaveClick(): Q.Promise<any>;
onRevertClick(): Q.Promise<any>;
/* Errors */
displayedError: ko.Observable<string>;
onSaveClick(): Q.Promise<any>;
onRevertClick(): Q.Promise<any>;
}
export interface WaitsForTemplate {
isTemplateReady: ko.Observable<boolean>;
}

View File

@@ -123,12 +123,4 @@ describe("Component Registerer", () => {
it("should register throughput-input component", () => {
expect(ko.components.isRegistered("throughput-input")).toBe(true);
});
it("should register library-manage-pane component", () => {
expect(ko.components.isRegistered("library-manage-pane")).toBe(true);
});
it("should register cluster-library-pane component", () => {
expect(ko.components.isRegistered("cluster-library-pane")).toBe(true);
});
});

View File

@@ -78,6 +78,4 @@ ko.components.register("browse-queries-pane", new PaneComponents.BrowseQueriesPa
ko.components.register("upload-file-pane", new PaneComponents.UploadFilePaneComponent());
ko.components.register("string-input-pane", new PaneComponents.StringInputPaneComponent());
ko.components.register("setup-notebooks-pane", new PaneComponents.SetupNotebooksPaneComponent());
ko.components.register("library-manage-pane", new PaneComponents.LibraryManagePaneComponent());
ko.components.register("cluster-library-pane", new PaneComponents.ClusterLibraryPaneComponent());
ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent());

View File

@@ -12,6 +12,10 @@ import AddTriggerIcon from "../../images/AddTrigger.svg";
import DeleteTriggerIcon from "../../images/DeleteTrigger.svg";
import DeleteUDFIcon from "../../images/DeleteUDF.svg";
import DeleteSprocIcon from "../../images/DeleteSproc.svg";
import Explorer from "./Explorer";
import UserDefinedFunction from "./Tree/UserDefinedFunction";
import StoredProcedure from "./Tree/StoredProcedure";
import Trigger from "./Tree/Trigger";
export interface CollectionContextMenuButtonParams {
databaseId: string;
@@ -26,7 +30,7 @@ export interface DatabaseContextMenuButtonParams {
*/
export class ResourceTreeContextMenuButtonFactory {
public static createDatabaseContextMenu(
container: ViewModels.Explorer,
container: Explorer,
selectedDatabase: ViewModels.Database
): TreeNodeMenuItem[] {
const newCollectionMenuItem: TreeNodeMenuItem = {
@@ -44,7 +48,7 @@ export class ResourceTreeContextMenuButtonFactory {
}
public static createCollectionContextMenuButton(
container: ViewModels.Explorer,
container: Explorer,
selectedCollection: ViewModels.Collection
): TreeNodeMenuItem[] {
const items: TreeNodeMenuItem[] = [];
@@ -115,8 +119,8 @@ export class ResourceTreeContextMenuButtonFactory {
}
public static createStoreProcedureContextMenuItems(
container: ViewModels.Explorer,
storedProcedure: ViewModels.StoredProcedure
container: Explorer,
storedProcedure: StoredProcedure
): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) {
return [];
@@ -131,10 +135,7 @@ export class ResourceTreeContextMenuButtonFactory {
];
}
public static createTriggerContextMenuItems(
container: ViewModels.Explorer,
trigger: ViewModels.Trigger
): TreeNodeMenuItem[] {
public static createTriggerContextMenuItems(container: Explorer, trigger: Trigger): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) {
return [];
}
@@ -149,8 +150,8 @@ export class ResourceTreeContextMenuButtonFactory {
}
public static createUserDefinedFunctionContextMenuItems(
container: ViewModels.Explorer,
userDefinedFunction: ViewModels.UserDefinedFunction
container: Explorer,
userDefinedFunction: UserDefinedFunction
): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) {
return [];

View File

@@ -23,8 +23,5 @@ interface ErrorDisplayParams {
}
class ErrorDisplayViewModel {
private params: ErrorDisplayParams;
public constructor(params: ErrorDisplayParams) {
this.params = params;
}
public constructor(public params: ErrorDisplayParams) {}
}

View File

@@ -1,6 +1,5 @@
import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "office-ui-fabric-react";
import * as React from "react";
import * as ViewModels from "../../../Contracts/ViewModels";
import * as Constants from "../../../Common/Constants";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import { RepoListItem } from "./GitHubReposComponent";
@@ -9,9 +8,10 @@ import * as GitHubUtils from "../../../Utils/GitHubUtils";
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import UrlUtility from "../../../Common/UrlUtility";
import Explorer from "../../Explorer";
export interface AddRepoComponentProps {
container: ViewModels.Explorer;
container: Explorer;
getRepo: (owner: string, repo: string) => Promise<IGitHubRepo>;
pinRepo: (item: RepoListItem) => void;
}

View File

@@ -1,40 +0,0 @@
import * as React from "react";
import { DetailsList, IColumn, SelectionMode } from "office-ui-fabric-react/lib/DetailsList";
import { Library } from "../../../Contracts/DataModels";
export interface ClusterLibraryItem extends Library {
installed: boolean;
}
export interface ClusterLibraryGridProps {
libraryItems: ClusterLibraryItem[];
onInstalledChanged: (libraryName: string, installed: boolean) => void;
}
export function ClusterLibraryGrid(props: ClusterLibraryGridProps): JSX.Element {
const onInstalledChanged = (e: React.FormEvent<HTMLInputElement>) => {
const target = e.target;
const libraryName = (target as any).dataset.name;
const checked = (target as any).checked;
return props.onInstalledChanged(libraryName, checked);
};
const columns: IColumn[] = [
{
key: "name",
name: "Name",
fieldName: "name",
minWidth: 150
},
{
key: "installed",
name: "Installed",
minWidth: 100,
onRender: (item: ClusterLibraryItem) => {
return <input type="checkbox" checked={item.installed} onChange={onInstalledChanged} data-name={item.name} />;
}
}
];
return <DetailsList columns={columns} items={props.libraryItems} selectionMode={SelectionMode.none} />;
}

View File

@@ -1,11 +0,0 @@
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { ClusterLibraryGrid, ClusterLibraryGridProps } from "./ClusterLibraryGrid";
export class ClusterLibraryGridAdapter implements ReactAdapter {
public parameters: ko.Observable<ClusterLibraryGridProps>;
public renderComponent(): JSX.Element {
return <ClusterLibraryGrid {...this.parameters()} />;
}
}

View File

@@ -1,156 +0,0 @@
import * as React from "react";
import DeleteIcon from "../../../../images/delete.svg";
import { Button } from "office-ui-fabric-react/lib/Button";
import { DetailsList, IColumn, SelectionMode } from "office-ui-fabric-react/lib/DetailsList";
import { Library } from "../../../Contracts/DataModels";
import { Label } from "office-ui-fabric-react/lib/Label";
import { SparkLibrary } from "../../../Common/Constants";
import { TextField } from "office-ui-fabric-react/lib/TextField";
export interface LibraryManageComponentProps {
addProps: {
nameProps: LibraryAddNameTextFieldProps;
urlProps: LibraryAddUrlTextFieldProps;
buttonProps: LibraryAddButtonProps;
};
gridProps: LibraryManageGridProps;
}
export function LibraryManageComponent(props: LibraryManageComponentProps): JSX.Element {
const {
addProps: { nameProps, urlProps, buttonProps },
gridProps
} = props;
return (
<div>
<div className="library-add-container">
<LibraryAddNameTextField {...nameProps} />
<LibraryAddUrlTextField {...urlProps} />
<LibraryAddButton {...buttonProps} />
</div>
<div className="library-grid-container">
<Label>All Libraries</Label>
<LibraryManageGrid {...gridProps} />
</div>
</div>
);
}
export interface LibraryManageGridProps {
items: Library[];
onLibraryDeleteClick: (libraryName: string) => void;
}
function LibraryManageGrid(props: LibraryManageGridProps): JSX.Element {
const columns: IColumn[] = [
{
key: "name",
name: "Name",
fieldName: "name",
minWidth: 200
},
{
key: "delete",
name: "Delete",
minWidth: 60,
onRender: (item: Library) => {
const onDelete = () => {
props.onLibraryDeleteClick(item.name);
};
return (
<span className="library-delete">
<img src={DeleteIcon} alt="Delete" onClick={onDelete} />
</span>
);
}
}
];
return <DetailsList columns={columns} items={props.items} selectionMode={SelectionMode.none} />;
}
export interface LibraryAddButtonProps {
disabled: boolean;
onLibraryAddClick: (event: React.FormEvent<any>) => void;
}
function LibraryAddButton(props: LibraryAddButtonProps): JSX.Element {
return (
<Button text="Add" className="library-add-button" onClick={props.onLibraryAddClick} disabled={props.disabled} />
);
}
export interface LibraryAddUrlTextFieldProps {
libraryAddress: string;
onLibraryAddressChange: (libraryAddress: string) => void;
onLibraryAddressValidated: (errorMessage: string, value: string) => void;
}
function LibraryAddUrlTextField(props: LibraryAddUrlTextFieldProps): JSX.Element {
const handleTextChange = (e: React.FormEvent<any>, libraryAddress: string) => {
props.onLibraryAddressChange(libraryAddress);
};
const validateText = (text: string): string => {
if (!text) {
return "";
}
const libraryUrlRegex = /^(https:\/\/.+\/)(.+)\.(jar)$/gi;
const isValidUrl = libraryUrlRegex.test(text);
if (isValidUrl) {
return "";
}
return "Need to be a valid https uri";
};
return (
<TextField
value={props.libraryAddress}
label="Url"
type="url"
className="library-add-textfield"
onChange={handleTextChange}
onGetErrorMessage={validateText}
onNotifyValidationResult={props.onLibraryAddressValidated}
placeholder="https://myrepo/myjar.jar"
autoComplete="off"
/>
);
}
export interface LibraryAddNameTextFieldProps {
libraryName: string;
onLibraryNameChange: (libraryName: string) => void;
onLibraryNameValidated: (errorMessage: string, value: string) => void;
}
function LibraryAddNameTextField(props: LibraryAddNameTextFieldProps): JSX.Element {
const handleTextChange = (e: React.FormEvent<any>, libraryName: string) => {
props.onLibraryNameChange(libraryName);
};
const validateText = (text: string): string => {
if (!text) {
return "";
}
const length = text.length;
if (length < SparkLibrary.nameMinLength || length > SparkLibrary.nameMaxLength) {
return "Library name length need to be between 3 and 63.";
}
const nameRegex = /^[a-z0-9][-a-z0-9]*[a-z0-9]$/gi;
const isValidUrl = nameRegex.test(text);
if (isValidUrl) {
return "";
}
return "Need to be a valid name. Letters, numbers and - are allowed";
};
return (
<TextField
value={props.libraryName}
label="Name"
type="text"
className="library-add-textfield"
onChange={handleTextChange}
onGetErrorMessage={validateText}
onNotifyValidationResult={props.onLibraryNameValidated}
placeholder="myjar"
autoComplete="off"
/>
);
}

View File

@@ -1,11 +0,0 @@
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { LibraryManageComponent, LibraryManageComponentProps } from "./LibraryManage";
export class LibraryManageComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<LibraryManageComponentProps>;
public renderComponent(): JSX.Element {
return <LibraryManageComponent {...this.parameters()} />;
}
}

View File

@@ -1,12 +1,12 @@
import * as React from "react";
import * as ViewModels from "../../../Contracts/ViewModels";
import { JunoClient, IGalleryItem } from "../../../Juno/JunoClient";
import { GalleryTab, SortBy, GalleryViewerComponentProps, GalleryViewerComponent } from "./GalleryViewerComponent";
import { NotebookViewerComponentProps, NotebookViewerComponent } from "../NotebookViewer/NotebookViewerComponent";
import * as GalleryUtils from "../../../Utils/GalleryUtils";
import Explorer from "../../Explorer";
export interface GalleryAndNotebookViewerComponentProps {
container?: ViewModels.Explorer;
container?: Explorer;
junoClient: JunoClient;
notebookUrl?: string;
galleryItem?: IGalleryItem;

View File

@@ -15,7 +15,6 @@ import {
} from "office-ui-fabric-react";
import * as React from "react";
import * as Logger from "../../../Common/Logger";
import * as ViewModels from "../../../Contracts/ViewModels";
import { IGalleryItem, JunoClient } from "../../../Juno/JunoClient";
import * as GalleryUtils from "../../../Utils/GalleryUtils";
import { NotificationConsoleUtils } from "../../../Utils/NotificationConsoleUtils";
@@ -24,9 +23,10 @@ import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComp
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
import "./GalleryViewerComponent.less";
import { HttpStatusCodes } from "../../../Common/Constants";
import Explorer from "../../Explorer";
export interface GalleryViewerComponentProps {
container?: ViewModels.Explorer;
container?: Explorer;
junoClient: JunoClient;
selectedTab: GalleryTab;
sortBy: SortBy;

View File

@@ -18,9 +18,10 @@ import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookRe
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
import "./NotebookViewerComponent.less";
import Explorer from "../../Explorer";
export interface NotebookViewerComponentProps {
container?: ViewModels.Explorer;
container?: Explorer;
junoClient?: JunoClient;
notebookUrl: string;
galleryItem?: IGalleryItem;

View File

@@ -217,7 +217,7 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
menuItem: any
) => {
if (window.confirm("Are you sure you want to delete this query?")) {
const container: ViewModels.Explorer = window.dataExplorer;
const container = window.dataExplorer;
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSavedQuery, {
databaseAccountName: container && container.databaseAccount().name,
defaultExperience: container && container.defaultExperience(),

View File

@@ -8,11 +8,12 @@ import * as React from "react";
import * as ViewModels from "../../../Contracts/ViewModels";
import { QueriesGridComponent, QueriesGridComponentProps } from "./QueriesGridComponent";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import Explorer from "../../Explorer";
export class QueriesGridComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(private container: ViewModels.Explorer) {
constructor(private container: Explorer) {
this.parameters = ko.observable<number>(Date.now());
}

View File

@@ -1,6 +1,6 @@
/* Utilities for validation */
export const onValidateValueChange = (newValue: string, minValue?: number, maxValue?: number): number => {
export const onValidateValueChange = (newValue: string, minValue?: number, maxValue?: number): number | undefined => {
let numericValue = parseInt(newValue);
if (!isNaN(numericValue) && isFinite(numericValue)) {
if (minValue !== undefined && numericValue < minValue) {
@@ -16,7 +16,7 @@ export const onValidateValueChange = (newValue: string, minValue?: number, maxVa
return undefined;
};
export const onIncrementValue = (newValue: string, step: number, max?: number): number => {
export const onIncrementValue = (newValue: string, step: number, max?: number): number | undefined => {
const numericValue = parseInt(newValue);
if (!isNaN(numericValue) && isFinite(numericValue)) {
const newValue = numericValue + step;
@@ -25,7 +25,7 @@ export const onIncrementValue = (newValue: string, step: number, max?: number):
return undefined;
};
export const onDecrementValue = (newValue: string, step: number, min?: number): number => {
export const onDecrementValue = (newValue: string, step: number, min?: number): number | undefined => {
const numericValue = parseInt(newValue);
if (!isNaN(numericValue) && isFinite(numericValue)) {
const newValue = numericValue - step;

View File

@@ -1,17 +0,0 @@
@import "../../../../less/Common/Constants";
.labelWithRedAsterisk {
line-height: 18px;
font-size: @DefaultFontSize;
font-family: @DataExplorerFont;
color: @DefaultFontColor;
}
.labelWithRedAsterisk::before {
content: "* ";
color: @SelectionHigh;
}
.clusterSettingsDropdown {
margin-bottom: 10px;
}

View File

@@ -1,159 +0,0 @@
import * as React from "react";
import { Dropdown, IDropdownOption, IDropdownProps } from "office-ui-fabric-react/lib/Dropdown";
import { Slider, ISliderProps } from "office-ui-fabric-react/lib/Slider";
import { Stack, IStackItemStyles, IStackStyles } from "office-ui-fabric-react/lib/Stack";
import { TextField, ITextFieldProps } from "office-ui-fabric-react/lib/TextField";
import { Spark } from "../../../Common/Constants";
import { SparkCluster } from "../../../Contracts/DataModels";
export interface ClusterSettingsComponentProps {
cluster: SparkCluster;
onClusterSettingsChanged: (cluster: SparkCluster) => void;
}
export class ClusterSettingsComponent extends React.Component<ClusterSettingsComponentProps, {}> {
constructor(props: ClusterSettingsComponentProps) {
super(props);
}
public render(): JSX.Element {
return (
<>
{this.getMasterSizeDropdown()}
{this.getWorkerSizeDropdown()}
{this.getWorkerCountSliderInput()}
</>
);
}
private getMasterSizeDropdown(): JSX.Element {
const driverSize: string =
this.props.cluster && this.props.cluster.properties && this.props.cluster.properties.driverSize;
const masterSizeOptions: IDropdownOption[] = Spark.SKUs.keys().map(sku => ({
key: sku,
text: Spark.SKUs.get(sku)
}));
const masterSizeDropdownProps: IDropdownProps = {
label: "Master Size",
options: masterSizeOptions,
defaultSelectedKey: driverSize,
onChange: this._onDriverSizeChange,
styles: {
root: "clusterSettingsDropdown"
}
};
return <Dropdown {...masterSizeDropdownProps} />;
}
private getWorkerSizeDropdown(): JSX.Element {
const workerSize: string =
this.props.cluster && this.props.cluster.properties && this.props.cluster.properties.workerSize;
const workerSizeOptions: IDropdownOption[] = Spark.SKUs.keys().map(sku => ({
key: sku,
text: Spark.SKUs.get(sku)
}));
const workerSizeDropdownProps: IDropdownProps = {
label: "Worker Size",
options: workerSizeOptions,
defaultSelectedKey: workerSize,
onChange: this._onWorkerSizeChange,
styles: {
label: "labelWithRedAsterisk",
root: "clusterSettingsDropdown"
}
};
return <Dropdown {...workerSizeDropdownProps} />;
}
private getWorkerCountSliderInput(): JSX.Element {
const workerCount: number =
(this.props.cluster &&
this.props.cluster.properties &&
this.props.cluster.properties.workerInstanceCount !== undefined &&
this.props.cluster.properties.workerInstanceCount) ||
0;
const stackStyle: IStackStyles = {
root: {
paddingTop: 5
}
};
const sliderItemStyle: IStackItemStyles = {
root: {
width: "100%",
paddingRight: 20
}
};
const workerCountSliderProps: ISliderProps = {
min: 0,
max: Spark.MaxWorkerCount,
step: 1,
value: workerCount,
showValue: false,
onChange: this._onWorkerCountChange,
styles: {
root: {
width: "100%",
paddingRight: 20
}
}
};
const workerCountTextFieldProps: ITextFieldProps = {
value: workerCount.toString(),
styles: {
fieldGroup: {
width: 45,
height: 25
},
field: {
textAlign: "center"
}
},
onChange: this._onWorkerCountTextFieldChange
};
return (
<Stack styles={stackStyle}>
<span className="labelWithRedAsterisk">Worker Nodes</span>
<Stack horizontal verticalAlign="center">
<Slider {...workerCountSliderProps} />
<TextField {...workerCountTextFieldProps} />
</Stack>
</Stack>
);
}
private _onDriverSizeChange = (_event: React.FormEvent, selectedOption: IDropdownOption) => {
const newValue: string = selectedOption.key as string;
const cluster = this.props.cluster;
if (cluster) {
cluster.properties.driverSize = newValue;
this.props.onClusterSettingsChanged(cluster);
}
};
private _onWorkerSizeChange = (_event: React.FormEvent, selectedOption: IDropdownOption) => {
const newValue: string = selectedOption.key as string;
const cluster = this.props.cluster;
if (cluster) {
cluster.properties.workerSize = newValue;
this.props.onClusterSettingsChanged(cluster);
}
};
private _onWorkerCountChange = (count: number) => {
count = Math.min(count, Spark.MaxWorkerCount);
const cluster = this.props.cluster;
if (cluster) {
cluster.properties.workerInstanceCount = count;
this.props.onClusterSettingsChanged(cluster);
}
};
private _onWorkerCountTextFieldChange = (_event: React.FormEvent, newValue: string) => {
const count = parseInt(newValue);
if (!isNaN(count)) {
this._onWorkerCountChange(count);
}
};
}

View File

@@ -1,11 +0,0 @@
import * as React from "react";
import { ClusterSettingsComponent, ClusterSettingsComponentProps } from "./ClusterSettingsComponent";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
export class ClusterSettingsComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<ClusterSettingsComponentProps>;
public renderComponent(): JSX.Element {
return <ClusterSettingsComponent {...this.parameters()} />;
}
}

View File

@@ -3,14 +3,14 @@ import * as sinon from "sinon";
import * as ViewModels from "../../Contracts/ViewModels";
import DocumentClientUtilityBase from "../../Common/DocumentClientUtilityBase";
import Q from "q";
import { CollectionStub, DatabaseStub, ExplorerStub } from "../OpenActionsStubs";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import { CosmosClient } from "../../Common/CosmosClient";
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
import Explorer from "../Explorer";
describe("ContainerSampleGenerator", () => {
const createExplorerStub = (database: ViewModels.Database): ExplorerStub => {
const explorerStub = new ExplorerStub();
const createExplorerStub = (database: ViewModels.Database): Explorer => {
const explorerStub = {} as Explorer;
explorerStub.nonSystemDatabases = ko.computed(() => [database]);
explorerStub.isPreferredApiGraph = ko.computed<boolean>(() => false);
explorerStub.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
@@ -53,11 +53,11 @@ describe("ContainerSampleGenerator", () => {
}
]
};
const collection = new CollectionStub({ id: ko.observable(sampleCollectionId) });
const database = new DatabaseStub({
const collection = { id: ko.observable(sampleCollectionId) } as ViewModels.Collection;
const database = {
id: ko.observable(sampleDatabaseId),
collections: ko.observableArray([collection])
});
collections: ko.observableArray<ViewModels.Collection>([collection])
} as ViewModels.Database;
database.findCollectionWithId = () => collection;
const explorerStub = createExplorerStub(database);
@@ -98,11 +98,11 @@ describe("ContainerSampleGenerator", () => {
"g.addV('person').property(id, '1').property('_partitionKey','pk').property('name', 'Eva').property('age', 44)"
]
};
const collection = new CollectionStub({ id: ko.observable(sampleCollectionId) });
const database = new DatabaseStub({
const collection = { id: ko.observable(sampleCollectionId) } as ViewModels.Collection;
const database = {
id: ko.observable(sampleDatabaseId),
collections: ko.observableArray([collection])
});
collections: ko.observableArray<ViewModels.Collection>([collection])
} as ViewModels.Database;
database.findCollectionWithId = () => collection;
collection.databaseId = database.id();

View File

@@ -6,6 +6,7 @@ import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsol
import { CosmosClient } from "../../Common/CosmosClient";
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import Explorer from "../Explorer";
interface SampleDataFile extends DataModels.CreateDatabaseAndCollectionRequest {
data: any[];
@@ -14,12 +15,12 @@ interface SampleDataFile extends DataModels.CreateDatabaseAndCollectionRequest {
export class ContainerSampleGenerator {
private sampleDataFile: SampleDataFile;
private constructor(private container: ViewModels.Explorer) {}
private constructor(private container: Explorer) {}
/**
* Factory function to load the json data file
*/
public static async createSampleGeneratorAsync(container: ViewModels.Explorer): Promise<ContainerSampleGenerator> {
public static async createSampleGeneratorAsync(container: Explorer): Promise<ContainerSampleGenerator> {
const generator = new ContainerSampleGenerator(container);
let dataFileContent: any;
if (container.isPreferredApiGraph()) {

View File

@@ -1,20 +1,21 @@
import { ExplorerStub, DatabaseStub, CollectionStub } from "../OpenActionsStubs";
import { DataSamplesUtil } from "./DataSamplesUtil";
import * as sinon from "sinon";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import * as ko from "knockout";
import Explorer from "../Explorer";
import { Database, Collection } from "../../Contracts/ViewModels";
describe("DataSampleUtils", () => {
const sampleCollectionId = "sampleCollectionId";
const sampleDatabaseId = "sampleDatabaseId";
it("should not create sample collection if collection already exists", async () => {
const collection = new CollectionStub({ id: ko.observable(sampleCollectionId) });
const database = new DatabaseStub({
const collection = { id: ko.observable(sampleCollectionId) } as Collection;
const database = {
id: ko.observable(sampleDatabaseId),
collections: ko.observableArray([collection])
});
const explorer = new ExplorerStub();
collections: ko.observableArray<Collection>([collection])
} as Database;
const explorer = {} as Explorer;
explorer.nonSystemDatabases = ko.computed(() => [database]);
explorer.showOkModalDialog = () => {};
const dataSamplesUtil = new DataSamplesUtil(explorer);

View File

@@ -2,10 +2,11 @@ import * as ViewModels from "../../Contracts/ViewModels";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import Explorer from "../Explorer";
export class DataSamplesUtil {
private static readonly DialogTitle = "Create Sample Container";
constructor(private container: ViewModels.Explorer) {}
constructor(private container: Explorer) {}
/**
* Check if Database/Container is already there: if so, show modal to delete

View File

@@ -24,7 +24,6 @@ import NewVertexPane from "./Panes/NewVertexPane";
import NotebookV2Tab from "./Tabs/NotebookV2Tab";
import Q from "q";
import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
import SparkMasterTab from "./Tabs/SparkMasterTab";
import TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import TerminalTab from "./Tabs/TerminalTab";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
@@ -36,7 +35,6 @@ import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
import { CassandraApi } from "../Api/Apis";
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
import { ClusterLibraryPane } from "./Panes/ClusterLibraryPane";
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
import { config } from "../Config";
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
@@ -52,7 +50,6 @@ import { FileSystemUtil } from "./Notebook/FileSystemUtil";
import { handleOpenAction } from "./OpenActions";
import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
import { IGalleryItem } from "../Juno/JunoClient";
import { LibraryManagePane } from "./Panes/LibraryManagePane";
import { LoadQueryPane } from "./Panes/LoadQueryPane";
import * as Logger from "../Common/Logger";
import { MessageHandler } from "../Common/MessageHandler";
@@ -72,7 +69,6 @@ import { RouteHandler } from "../RouteHandlers/RouteHandler";
import { SaveQueryPane } from "./Panes/SaveQueryPane";
import { SettingsPane } from "./Panes/SettingsPane";
import { SetupNotebooksPane } from "./Panes/SetupNotebooksPane";
import { SparkClusterManager } from "../SparkClusterManager/SparkClusterManager";
import { SplashScreenComponentAdapter } from "./SplashScreen/SplashScreenComponentApdapter";
import { Splitter, SplitterBounds, SplitterDirection } from "../Common/Splitter";
import { StringInputPane } from "./Panes/StringInputPane";
@@ -83,6 +79,9 @@ import { UploadItemsPane } from "./Panes/UploadItemsPane";
import { UploadItemsPaneAdapter } from "./Panes/UploadItemsPaneAdapter";
import { ReactAdapter } from "../Bindings/ReactBindingHandler";
import { toRawContentUri, fromContentUri } from "../Utils/GitHubUtils";
import UserDefinedFunction from "./Tree/UserDefinedFunction";
import StoredProcedure from "./Tree/StoredProcedure";
import Trigger from "./Tree/Trigger";
BindingHandlersRegisterer.registerBindingHandlers();
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
@@ -93,7 +92,7 @@ enum ShareAccessToggleState {
Read
}
export default class Explorer implements ViewModels.Explorer {
export default class Explorer {
public flight: ko.Observable<string> = ko.observable<string>(
SharedConstants.CollectionCreation.DefaultAddCollectionDefaultFlight
);
@@ -168,30 +167,28 @@ export default class Explorer implements ViewModels.Explorer {
public tabsManager: TabsManager;
// Contextual panes
public addDatabasePane: ViewModels.AddDatabasePane;
public addCollectionPane: ViewModels.AddCollectionPane;
public deleteCollectionConfirmationPane: ViewModels.DeleteCollectionConfirmationPane;
public deleteDatabaseConfirmationPane: ViewModels.DeleteDatabaseConfirmationPane;
public graphStylingPane: ViewModels.GraphStylingPane;
public addTableEntityPane: ViewModels.AddTableEntityPane;
public editTableEntityPane: ViewModels.EditTableEntityPane;
public addDatabasePane: AddDatabasePane;
public addCollectionPane: AddCollectionPane;
public deleteCollectionConfirmationPane: DeleteCollectionConfirmationPane;
public deleteDatabaseConfirmationPane: DeleteDatabaseConfirmationPane;
public graphStylingPane: GraphStylingPane;
public addTableEntityPane: AddTableEntityPane;
public editTableEntityPane: EditTableEntityPane;
public tableColumnOptionsPane: TableColumnOptionsPane;
public querySelectPane: QuerySelectPane;
public newVertexPane: ViewModels.NewVertexPane;
public cassandraAddCollectionPane: ViewModels.CassandraAddCollectionPane;
public settingsPane: ViewModels.SettingsPane;
public executeSprocParamsPane: ViewModels.ExecuteSprocParamsPane;
public renewAdHocAccessPane: ViewModels.RenewAdHocAccessPane;
public uploadItemsPane: ViewModels.UploadItemsPane;
public newVertexPane: NewVertexPane;
public cassandraAddCollectionPane: CassandraAddCollectionPane;
public settingsPane: SettingsPane;
public executeSprocParamsPane: ExecuteSprocParamsPane;
public renewAdHocAccessPane: RenewAdHocAccessPane;
public uploadItemsPane: UploadItemsPane;
public uploadItemsPaneAdapter: UploadItemsPaneAdapter;
public loadQueryPane: ViewModels.LoadQueryPane;
public loadQueryPane: LoadQueryPane;
public saveQueryPane: ViewModels.ContextualPane;
public browseQueriesPane: ViewModels.BrowseQueriesPane;
public browseQueriesPane: BrowseQueriesPane;
public uploadFilePane: UploadFilePane;
public stringInputPane: StringInputPane;
public setupNotebooksPane: SetupNotebooksPane;
public libraryManagePane: ViewModels.ContextualPane;
public clusterLibraryPane: ViewModels.ContextualPane;
public gitHubReposPane: ViewModels.ContextualPane;
public publishNotebookPaneAdapter: ReactAdapter;
@@ -206,7 +203,7 @@ export default class Explorer implements ViewModels.Explorer {
public shouldShowShareDialogContents: ko.Observable<boolean>;
public shareAccessData: ko.Observable<ViewModels.AdHocAccessData>;
public renewExplorerShareAccess: (explorer: ViewModels.Explorer, token: string) => Q.Promise<void>;
public renewExplorerShareAccess: (explorer: Explorer, token: string) => Q.Promise<void>;
public renewTokenError: ko.Observable<string>;
public tokenForRenewal: ko.Observable<string>;
public shareAccessToggleState: ko.Observable<ShareAccessToggleState>;
@@ -220,8 +217,7 @@ export default class Explorer implements ViewModels.Explorer {
public isNotebookEnabled: ko.Observable<boolean>;
public isNotebooksEnabledForAccount: ko.Observable<boolean>;
public notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>;
public notebookWorkspaceManager: ViewModels.NotebookWorkspaceManager;
public sparkClusterManager: ViewModels.SparkClusterManager;
public notebookWorkspaceManager: NotebookWorkspaceManager;
public sparkClusterConnectionInfo: ko.Observable<DataModels.SparkClusterConnectionInfo>;
public isSparkEnabled: ko.Observable<boolean>;
public isSparkEnabledForAccount: ko.Observable<boolean>;
@@ -238,7 +234,7 @@ export default class Explorer implements ViewModels.Explorer {
private _isInitializingNotebooks: boolean;
private _isInitializingSparkConnectionInfo: boolean;
private notebookBasePath: ko.Observable<string>;
private _arcadiaManager: ViewModels.ArcadiaResourceManager;
private _arcadiaManager: ArcadiaResourceManager;
private notebookToImport: {
name: string;
content: string;
@@ -313,7 +309,6 @@ export default class Explorer implements ViewModels.Explorer {
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(true);
RouteHandler.getInstance().initHandler();
this.notebookWorkspaceManager = new NotebookWorkspaceManager(this.armEndpoint());
this.sparkClusterManager = new SparkClusterManager(this.armEndpoint());
this.arcadiaWorkspaces = ko.observableArray();
this._arcadiaManager = new ArcadiaResourceManager(this.armEndpoint());
this._isAfecFeatureRegistered(Constants.AfecFeatures.StorageAnalytics).then(isRegistered =>
@@ -750,22 +745,6 @@ export default class Explorer implements ViewModels.Explorer {
container: this
});
this.libraryManagePane = new LibraryManagePane({
documentClientUtility: this.documentClientUtility,
id: "libraryManagePane",
visible: ko.observable<boolean>(false),
container: this
});
this.clusterLibraryPane = new ClusterLibraryPane({
documentClientUtility: this.documentClientUtility,
id: "clusterLibraryPane",
visible: ko.observable<boolean>(false),
container: this
});
this.tabsManager = new TabsManager();
this._panes = [
@@ -1607,70 +1586,6 @@ export default class Explorer implements ViewModels.Explorer {
window.open(Constants.Urls.feedbackEmail, "_self");
};
public async initSparkConnectionInfo(databaseAccount: DataModels.DatabaseAccount) {
if (!databaseAccount) {
throw new Error("No database account specified");
}
if (this._isInitializingSparkConnectionInfo) {
return;
}
this._isInitializingSparkConnectionInfo = true;
let connectionInfo: DataModels.SparkClusterConnectionInfo;
try {
connectionInfo = await this.sparkClusterManager.getClusterConnectionInfoAsync(databaseAccount.id, "default");
} catch (error) {
this._isInitializingSparkConnectionInfo = false;
Logger.logError(error, "initSparkConnectionInfo/getClusterConnectionInfoAsync");
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to get cluster connection info: ${JSON.stringify(error)}`
);
throw error;
} finally {
// Overwrite with feature flags
if (this.isFeatureEnabled(Constants.Features.livyEndpoint)) {
connectionInfo = {
userName: undefined,
password: undefined,
endpoints: [
{
kind: DataModels.SparkClusterEndpointKind.Livy,
endpoint: this.features()[Constants.Features.livyEndpoint]
}
]
};
}
}
this.sparkClusterConnectionInfo(connectionInfo);
this.sparkClusterConnectionInfo.valueHasMutated();
this._isInitializingSparkConnectionInfo = false;
}
public deleteCluster() {
if (!this.isSparkEnabled() || !this.sparkClusterManager) {
return;
}
const deleteClusterDialogProps: DialogProps = {
isModal: true,
visible: true,
title: "Delete Cluster",
subText:
"This will delete the default cluster associated with this account and interrupt any scheduled jobs. Proceed anyway?",
primaryButtonText: "OK",
secondaryButtonText: "Cancel",
onPrimaryButtonClick: async () => {
this._closeModalDialog();
await this._deleteCluster();
},
onSecondaryButtonClick: this._closeModalDialog
};
this._dialogProps(deleteClusterDialogProps);
}
public async getArcadiaToken(): Promise<string> {
return new Promise<string>((resolve: (token: string) => void, reject: (error: any) => void) => {
MessageHandler.sendCachedDataMessage<string>(MessageTypes.GetArcadiaToken, undefined /** params **/).then(
@@ -1821,53 +1736,6 @@ export default class Explorer implements ViewModels.Explorer {
}
}
private _deleteCluster = async () => {
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSparkCluster, {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
});
const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
"Deleting the default spark cluster associated with this account"
);
try {
await this.sparkClusterManager.deleteClusterAsync(this.databaseAccount().id, "default");
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
"Successfully deleted the default spark cluster associated with this account"
);
TelemetryProcessor.traceSuccess(
Action.DeleteSparkCluster,
{
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree
},
startKey
);
} catch (error) {
const errorMessage = JSON.stringify(error);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to delete default spark cluster: ${errorMessage}`
);
TelemetryProcessor.traceFailure(
Action.DeleteSparkCluster,
{
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
error,
errorMessage
},
startKey
);
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(id);
}
};
private _resetNotebookWorkspace = async () => {
this._closeModalDialog();
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Resetting notebook workspace");
@@ -2090,9 +1958,9 @@ export default class Explorer implements ViewModels.Explorer {
}
// TODO: Refactor below methods, minimize dependencies and add unit tests where necessary
public findSelectedStoredProcedure(): ViewModels.StoredProcedure {
public findSelectedStoredProcedure(): StoredProcedure {
const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
return _.find(selectedCollection.storedProcedures(), (storedProcedure: ViewModels.StoredProcedure) => {
return _.find(selectedCollection.storedProcedures(), (storedProcedure: StoredProcedure) => {
const openedSprocTab = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.StoredProcedures,
(tab: ViewModels.Tab) => tab.node && tab.node.rid === storedProcedure.rid
@@ -2104,9 +1972,9 @@ export default class Explorer implements ViewModels.Explorer {
});
}
public findSelectedUDF(): ViewModels.UserDefinedFunction {
public findSelectedUDF(): UserDefinedFunction {
const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
return _.find(selectedCollection.userDefinedFunctions(), (userDefinedFunction: ViewModels.UserDefinedFunction) => {
return _.find(selectedCollection.userDefinedFunctions(), (userDefinedFunction: UserDefinedFunction) => {
const openedUdfTab = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.UserDefinedFunctions,
(tab: ViewModels.Tab) => tab.node && tab.node.rid === userDefinedFunction.rid
@@ -2118,9 +1986,9 @@ export default class Explorer implements ViewModels.Explorer {
});
}
public findSelectedTrigger(): ViewModels.Trigger {
public findSelectedTrigger(): Trigger {
const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
return _.find(selectedCollection.triggers(), (trigger: ViewModels.Trigger) => {
return _.find(selectedCollection.triggers(), (trigger: Trigger) => {
const openedTriggerTab = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.Triggers,
(tab: ViewModels.Tab) => tab.node && tab.node.rid === trigger.rid
@@ -2835,42 +2703,6 @@ export default class Explorer implements ViewModels.Explorer {
return false;
}
};
public async openSparkMasterTab() {
if (!this.sparkClusterConnectionInfo()) {
await this.initSparkConnectionInfo(this.databaseAccount());
}
const sparkMasterTabs: SparkMasterTab[] = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.SparkMasterTab
) as SparkMasterTab[];
let sparkMasterTab: SparkMasterTab = sparkMasterTabs && sparkMasterTabs[0];
if (sparkMasterTab) {
this.tabsManager.activateTab(sparkMasterTab);
} else {
sparkMasterTab = new SparkMasterTab({
clusterConnectionInfo: this.sparkClusterConnectionInfo(),
tabKind: ViewModels.CollectionTabKind.SparkMasterTab,
node: null,
title: "Apache Spark",
tabPath: "",
documentClientUtility: null,
collection: null,
selfLink: null,
hashLocation: "sparkmaster",
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this
});
this.tabsManager.activateNewTab(sparkMasterTab);
}
}
private refreshNotebookList = async (): Promise<void> => {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
return;

View File

@@ -11,14 +11,15 @@ import { CommandBarComponentButtonFactory } from "./CommandBarComponentButtonFac
import { CommandBar, ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
import { StyleConstants } from "../../../Common/Constants";
import { CommandBarUtil } from "./CommandBarUtil";
import Explorer from "../../Explorer";
export class CommandBarComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
public container: ViewModels.Explorer;
public container: Explorer;
private tabsButtons: ViewModels.NavbarButtonConfig[];
private isNotebookTabActive: ko.Computed<boolean>;
constructor(container: ViewModels.Explorer) {
constructor(container: Explorer) {
this.container = container;
this.tabsButtons = [];
this.isNotebookTabActive = ko.computed(() =>

View File

@@ -1,24 +1,25 @@
import * as ko from "knockout";
import * as ViewModels from "../../../Contracts/ViewModels";
import { CommandBarComponentButtonFactory } from "./CommandBarComponentButtonFactory";
import { ExplorerStub } from "../../OpenActionsStubs";
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
import NotebookManager from "../../Notebook/NotebookManager";
import Explorer from "../../Explorer";
describe("CommandBarComponentButtonFactory tests", () => {
let mockExplorer: ViewModels.Explorer;
let mockExplorer: Explorer;
describe("Enable notebook button", () => {
const enableNotebookBtnLabel = "Enable Notebooks (Preview)";
beforeAll(() => {
mockExplorer = new ExplorerStub();
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
@@ -75,12 +76,13 @@ describe("CommandBarComponentButtonFactory tests", () => {
const openMongoShellBtnLabel = "Open Mongo Shell";
beforeAll(() => {
mockExplorer = new ExplorerStub();
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
@@ -155,11 +157,12 @@ describe("CommandBarComponentButtonFactory tests", () => {
const openCassandraShellBtnLabel = "Open Cassandra Shell";
beforeAll(() => {
mockExplorer = new ExplorerStub();
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
@@ -236,13 +239,14 @@ describe("CommandBarComponentButtonFactory tests", () => {
const manageGitHubSettingsBtnLabel = "Manage GitHub settings";
beforeAll(() => {
mockExplorer = new ExplorerStub();
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
@@ -294,7 +298,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
describe("Resource token", () => {
beforeAll(() => {
mockExplorer = new ExplorerStub();
mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(true);
mockExplorer.isPreferredApiDocumentDB = ko.computed(() => true);

View File

@@ -25,11 +25,12 @@ import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-works
import GitHubIcon from "../../../../images/github.svg";
import SynapseIcon from "../../../../images/synapse-link.svg";
import { config, Platform } from "../../../Config";
import Explorer from "../../Explorer";
export class CommandBarComponentButtonFactory {
private static counter: number = 0;
public static createStaticCommandBarButtons(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig[] {
public static createStaticCommandBarButtons(container: Explorer): ViewModels.NavbarButtonConfig[] {
if (container.isAuthWithResourceToken()) {
return CommandBarComponentButtonFactory.createStaticCommandBarButtonsForResourceToken(container);
}
@@ -132,7 +133,7 @@ export class CommandBarComponentButtonFactory {
return buttons;
}
public static createContextCommandBarButtons(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig[] {
public static createContextCommandBarButtons(container: Explorer): ViewModels.NavbarButtonConfig[] {
const buttons: ViewModels.NavbarButtonConfig[] = [];
if (!container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()) {
@@ -155,7 +156,7 @@ export class CommandBarComponentButtonFactory {
return buttons;
}
public static createControlCommandBarButtons(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig[] {
public static createControlCommandBarButtons(container: Explorer): ViewModels.NavbarButtonConfig[] {
const buttons: ViewModels.NavbarButtonConfig[] = [];
if (window.dataExplorerPlatform === PlatformType.Hosted) {
return buttons;
@@ -223,11 +224,11 @@ export class CommandBarComponentButtonFactory {
};
}
private static areScriptsSupported(container: ViewModels.Explorer): boolean {
private static areScriptsSupported(container: Explorer): boolean {
return container.isPreferredApiDocumentDB() || container.isPreferredApiGraph();
}
private static createNewCollectionGroup(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
private static createNewCollectionGroup(container: Explorer): ViewModels.NavbarButtonConfig {
const label = container.addCollectionText();
return {
iconSrc: AddCollectionIcon,
@@ -240,7 +241,7 @@ export class CommandBarComponentButtonFactory {
};
}
private static createOpenSynapseLinkDialogButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
private static createOpenSynapseLinkDialogButton(container: Explorer): ViewModels.NavbarButtonConfig {
if (config.platform === Platform.Emulator) {
return null;
}
@@ -275,7 +276,7 @@ export class CommandBarComponentButtonFactory {
};
}
private static createNewDatabase(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
private static createNewDatabase(container: Explorer): ViewModels.NavbarButtonConfig {
const label = container.addDatabaseText();
return {
iconSrc: AddDatabaseIcon,
@@ -290,7 +291,7 @@ export class CommandBarComponentButtonFactory {
};
}
private static createNewSQLQueryButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
private static createNewSQLQueryButton(container: Explorer): ViewModels.NavbarButtonConfig {
if (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) {
const label = "New SQL Query";
return {
@@ -324,7 +325,7 @@ export class CommandBarComponentButtonFactory {
return null;
}
public static createScriptCommandButtons(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig[] {
public static createScriptCommandButtons(container: Explorer): ViewModels.NavbarButtonConfig[] {
const buttons: ViewModels.NavbarButtonConfig[] = [];
const shouldEnableScriptsCommands: boolean =
@@ -384,7 +385,7 @@ export class CommandBarComponentButtonFactory {
return buttons;
}
private static createScaleAndSettingsButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
private static createScaleAndSettingsButton(container: Explorer): ViewModels.NavbarButtonConfig {
let isShared = false;
if (container.isDatabaseNodeSelected()) {
isShared = container.findSelectedDatabase().isDatabaseShared();
@@ -409,7 +410,7 @@ export class CommandBarComponentButtonFactory {
};
}
private static createNewNotebookButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
private static createNewNotebookButton(container: Explorer): ViewModels.NavbarButtonConfig {
const label = "New Notebook";
return {
iconSrc: NewNotebookIcon,
@@ -422,7 +423,7 @@ export class CommandBarComponentButtonFactory {
};
}
private static createuploadNotebookButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
private static createuploadNotebookButton(container: Explorer): ViewModels.NavbarButtonConfig {
const label = "Upload to Notebook Server";
return {
iconSrc: NewNotebookIcon,
@@ -435,7 +436,7 @@ export class CommandBarComponentButtonFactory {
};
}
private static createOpenQueryButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
private static createOpenQueryButton(container: Explorer): ViewModels.NavbarButtonConfig {
const label = "Open Query";
return {
iconSrc: BrowseQueriesIcon,
@@ -448,7 +449,7 @@ export class CommandBarComponentButtonFactory {
};
}
private static createOpenQueryFromDiskButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
private static createOpenQueryFromDiskButton(container: Explorer): ViewModels.NavbarButtonConfig {
const label = "Open Query From Disk";
return {
iconSrc: OpenQueryFromDiskIcon,
@@ -461,7 +462,7 @@ export class CommandBarComponentButtonFactory {
};
}
private static createEnableNotebooksButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
private static createEnableNotebooksButton(container: Explorer): ViewModels.NavbarButtonConfig {
if (config.platform === Platform.Emulator) {
return null;
}
@@ -482,7 +483,7 @@ export class CommandBarComponentButtonFactory {
};
}
private static createOpenTerminalButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
private static createOpenTerminalButton(container: Explorer): ViewModels.NavbarButtonConfig {
const label = "Open Terminal";
return {
iconSrc: CosmosTerminalIcon,
@@ -495,7 +496,7 @@ export class CommandBarComponentButtonFactory {
};
}
private static createOpenMongoTerminalButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
private static createOpenMongoTerminalButton(container: Explorer): ViewModels.NavbarButtonConfig {
const label = "Open Mongo Shell";
const tooltip =
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
@@ -521,7 +522,7 @@ export class CommandBarComponentButtonFactory {
};
}
private static createOpenCassandraTerminalButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
private static createOpenCassandraTerminalButton(container: Explorer): ViewModels.NavbarButtonConfig {
const label = "Open Cassandra Shell";
const tooltip =
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks.";
@@ -547,7 +548,7 @@ export class CommandBarComponentButtonFactory {
};
}
private static createNotebookWorkspaceResetButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
private static createNotebookWorkspaceResetButton(container: Explorer): ViewModels.NavbarButtonConfig {
const label = "Reset Workspace";
return {
iconSrc: ResetWorkspaceIcon,
@@ -560,7 +561,7 @@ export class CommandBarComponentButtonFactory {
};
}
private static createManageGitHubAccountButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
private static createManageGitHubAccountButton(container: Explorer): ViewModels.NavbarButtonConfig {
let connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn();
const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub";
return {
@@ -583,9 +584,7 @@ export class CommandBarComponentButtonFactory {
};
}
private static createStaticCommandBarButtonsForResourceToken(
container: ViewModels.Explorer
): ViewModels.NavbarButtonConfig[] {
private static createStaticCommandBarButtonsForResourceToken(container: Explorer): ViewModels.NavbarButtonConfig[] {
const newSqlQueryBtn = CommandBarComponentButtonFactory.createNewSQLQueryButton(container);
const openQueryBtn = CommandBarComponentButtonFactory.createOpenQueryButton(container);

View File

@@ -4,13 +4,14 @@ import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import * as ViewModels from "../../../Contracts/ViewModels";
import { NotificationConsoleComponent } from "./NotificationConsoleComponent";
import { ConsoleData } from "./NotificationConsoleComponent";
import Explorer from "../../Explorer";
export class NotificationConsoleComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
public container: ViewModels.Explorer;
public container: Explorer;
private consoleData: ko.ObservableArray<ConsoleData>;
constructor(container: ViewModels.Explorer) {
constructor(container: Explorer) {
this.container = container;
this.consoleData = container.notificationConsoleData;

View File

@@ -3,6 +3,7 @@ import { StorageKey, LocalStorageUtility } from "../../Shared/StorageUtility";
import CollectionIcon from "../../../images/tree-collection.svg";
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
import Explorer from "../Explorer";
export enum Type {
OpenCollection,
@@ -36,7 +37,7 @@ export class MostRecentActivity {
private static readonly schemaVersion: string = "1";
private static itemsMaxNumber: number = 5;
private storedData: StoredData;
constructor(private container: ViewModels.Explorer) {
constructor(private container: Explorer) {
// Retrieve from local storage
if (LocalStorageUtility.hasItem(StorageKey.MostRecentActivity)) {
const rawData = LocalStorageUtility.getEntryString(StorageKey.MostRecentActivity);

View File

@@ -131,7 +131,7 @@ export class NotebookContainerClient implements ViewModels.INotebookContainerCli
}
private async recreateNotebookWorkspaceAsync(): Promise<void> {
const explorer = window.dataExplorer as ViewModels.Explorer;
const explorer = window.dataExplorer;
if (!explorer || !explorer.databaseAccount() || !explorer.databaseAccount().id) {
throw new Error("DataExplorer not initialized");
}

View File

@@ -23,9 +23,10 @@ import { DialogProps } from "../Controls/DialogReactComponent/DialogComponent";
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
import { PublishNotebookPaneAdapter } from "../Panes/PublishNotebookPaneAdapter";
import { getFullName } from "../../Utils/UserUtils";
import Explorer from "../Explorer";
export interface NotebookManagerOptions {
container: ViewModels.Explorer;
container: Explorer;
notebookBasePath: ko.Observable<string>;
dialogProps: ko.Observable<DialogProps>;
resourceTree: ResourceTreeAdapter;

View File

@@ -1,55 +1,43 @@
import * as ko from "knockout";
import { handleOpenAction } from "./OpenActions";
import * as ViewModels from "../Contracts/ViewModels";
import {
ExplorerStub,
DatabaseStub,
CollectionStub,
AddCollectionPaneStub,
CassandraAddCollectionPane
} from "./OpenActionsStubs";
import { ActionContracts } from "../Contracts/ExplorerContracts";
import Explorer from "./Explorer";
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
import AddCollectionPane from "./Panes/AddCollectionPane";
describe("OpenActions", () => {
describe("handleOpenAction", () => {
let explorer: ViewModels.Explorer;
let explorer: Explorer;
let database: ViewModels.Database;
let collection: ViewModels.Collection;
let databases: ViewModels.Database[];
let expandCollection: jasmine.Spy;
let onDocumentDBDocumentsClick: jasmine.Spy;
let onMongoDBDocumentsClick: jasmine.Spy;
let onTableEntitiesClick: jasmine.Spy;
let onGraphDocumentsClick: jasmine.Spy;
let onNewQueryClick: jasmine.Spy;
let onSettingsClick: jasmine.Spy;
let openAddCollectionPane: jasmine.Spy;
let openCassandraAddCollectionPane: jasmine.Spy;
beforeEach(() => {
explorer = new ExplorerStub();
explorer.addCollectionPane = new AddCollectionPaneStub();
explorer.cassandraAddCollectionPane = new CassandraAddCollectionPane();
explorer = {} as Explorer;
explorer.addCollectionPane = {} as AddCollectionPane;
explorer.addCollectionPane.open = jest.fn();
explorer.cassandraAddCollectionPane = {} as CassandraAddCollectionPane;
explorer.cassandraAddCollectionPane.open = jest.fn();
explorer.closeAllPanes = () => {};
explorer.isConnectExplorerVisible = () => false;
database = new DatabaseStub({
database = {
id: ko.observable("db"),
collections: ko.observableArray<ViewModels.Collection>([])
});
} as ViewModels.Database;
databases = [database];
collection = new CollectionStub({
collection = {
id: ko.observable("coll")
});
} as ViewModels.Collection;
expandCollection = spyOn(collection, "expandCollection");
onDocumentDBDocumentsClick = spyOn(collection, "onDocumentDBDocumentsClick");
onMongoDBDocumentsClick = spyOn(collection, "onMongoDBDocumentsClick");
onTableEntitiesClick = spyOn(collection, "onTableEntitiesClick");
onGraphDocumentsClick = spyOn(collection, "onGraphDocumentsClick");
onNewQueryClick = spyOn(collection, "onNewQueryClick");
onSettingsClick = spyOn(collection, "onSettingsClick");
openAddCollectionPane = spyOn(explorer.addCollectionPane, "open");
openCassandraAddCollectionPane = spyOn(explorer.cassandraAddCollectionPane, "open");
collection.expandCollection = jest.fn();
collection.onDocumentDBDocumentsClick = jest.fn();
collection.onMongoDBDocumentsClick = jest.fn();
collection.onTableEntitiesClick = jest.fn();
collection.onGraphDocumentsClick = jest.fn();
collection.onNewQueryClick = jest.fn();
collection.onSettingsClick = jest.fn();
});
describe("unknown action type", () => {
@@ -87,7 +75,7 @@ describe("OpenActions", () => {
};
const actionHandled = handleOpenAction(action, [], explorer);
expect(openCassandraAddCollectionPane).toHaveBeenCalled();
expect(explorer.cassandraAddCollectionPane.open).toHaveBeenCalled();
});
it("enum value should call cassandraAddCollectionPane.open", () => {
@@ -97,7 +85,7 @@ describe("OpenActions", () => {
};
const actionHandled = handleOpenAction(action, [], explorer);
expect(openCassandraAddCollectionPane).toHaveBeenCalled();
expect(explorer.cassandraAddCollectionPane.open).toHaveBeenCalled();
});
});
@@ -109,7 +97,7 @@ describe("OpenActions", () => {
};
const actionHandled = handleOpenAction(action, [], explorer);
expect(openAddCollectionPane).toHaveBeenCalled();
expect(explorer.addCollectionPane.open).toHaveBeenCalled();
});
it("enum value should call addCollectionPane.open", () => {
@@ -119,7 +107,7 @@ describe("OpenActions", () => {
};
const actionHandled = handleOpenAction(action, [], explorer);
expect(openAddCollectionPane).toHaveBeenCalled();
expect(explorer.addCollectionPane.open).toHaveBeenCalled();
});
});
});
@@ -149,10 +137,10 @@ describe("OpenActions", () => {
};
handleOpenAction(action, [database], explorer);
expect(expandCollection).not.toHaveBeenCalled();
expect(collection.expandCollection).not.toHaveBeenCalled();
database.collections([collection]);
expect(expandCollection).toHaveBeenCalled();
expect(collection.expandCollection).toHaveBeenCalled();
});
it("should expand collection node when handleOpenAction is called", () => {
@@ -164,7 +152,7 @@ describe("OpenActions", () => {
database.collections([collection]);
handleOpenAction(action, [database], explorer);
expect(expandCollection).toHaveBeenCalled();
expect(collection.expandCollection).toHaveBeenCalled();
});
describe("SQLDocuments tab kind", () => {
@@ -177,10 +165,10 @@ describe("OpenActions", () => {
};
handleOpenAction(action, [database], explorer);
expect(onDocumentDBDocumentsClick).not.toHaveBeenCalled();
expect(collection.onDocumentDBDocumentsClick).not.toHaveBeenCalled();
database.collections([collection]);
expect(onDocumentDBDocumentsClick).toHaveBeenCalled();
expect(collection.onDocumentDBDocumentsClick).toHaveBeenCalled();
});
it("string value should call onDocumentDBDocumentsClick", () => {
@@ -193,7 +181,7 @@ describe("OpenActions", () => {
database.collections([collection]);
handleOpenAction(action, [database], explorer);
expect(onDocumentDBDocumentsClick).toHaveBeenCalled();
expect(collection.onDocumentDBDocumentsClick).toHaveBeenCalled();
});
it("enum value should call onDocumentDBDocumentsClick before collections are fetched", () => {
@@ -205,10 +193,10 @@ describe("OpenActions", () => {
};
handleOpenAction(action, [database], explorer);
expect(onDocumentDBDocumentsClick).not.toHaveBeenCalled();
expect(collection.onDocumentDBDocumentsClick).not.toHaveBeenCalled();
database.collections([collection]);
expect(onDocumentDBDocumentsClick).toHaveBeenCalled();
expect(collection.onDocumentDBDocumentsClick).toHaveBeenCalled();
});
it("enum value should call onDocumentDBDocumentsClick", () => {
@@ -221,7 +209,7 @@ describe("OpenActions", () => {
database.collections([collection]);
handleOpenAction(action, [database], explorer);
expect(onDocumentDBDocumentsClick).toHaveBeenCalled();
expect(collection.onDocumentDBDocumentsClick).toHaveBeenCalled();
});
});
@@ -235,10 +223,10 @@ describe("OpenActions", () => {
};
handleOpenAction(action, [database], explorer);
expect(onMongoDBDocumentsClick).not.toHaveBeenCalled();
expect(collection.onMongoDBDocumentsClick).not.toHaveBeenCalled();
database.collections([collection]);
expect(onMongoDBDocumentsClick).toHaveBeenCalled();
expect(collection.onMongoDBDocumentsClick).toHaveBeenCalled();
});
it("string value should call onMongoDBDocumentsClick", () => {
@@ -251,7 +239,7 @@ describe("OpenActions", () => {
database.collections([collection]);
handleOpenAction(action, [database], explorer);
expect(onMongoDBDocumentsClick).toHaveBeenCalled();
expect(collection.onMongoDBDocumentsClick).toHaveBeenCalled();
});
it("enum value should call onMongoDBDocumentsClick before collections are fetched", () => {
@@ -263,10 +251,10 @@ describe("OpenActions", () => {
};
handleOpenAction(action, [database], explorer);
expect(onMongoDBDocumentsClick).not.toHaveBeenCalled();
expect(collection.onMongoDBDocumentsClick).not.toHaveBeenCalled();
database.collections([collection]);
expect(onMongoDBDocumentsClick).toHaveBeenCalled();
expect(collection.onMongoDBDocumentsClick).toHaveBeenCalled();
});
it("enum value should call onMongoDBDocumentsClick", () => {
@@ -279,7 +267,7 @@ describe("OpenActions", () => {
database.collections([collection]);
handleOpenAction(action, [database], explorer);
expect(onMongoDBDocumentsClick).toHaveBeenCalled();
expect(collection.onMongoDBDocumentsClick).toHaveBeenCalled();
});
});
@@ -293,10 +281,10 @@ describe("OpenActions", () => {
};
handleOpenAction(action, [database], explorer);
expect(onTableEntitiesClick).not.toHaveBeenCalled();
expect(collection.onTableEntitiesClick).not.toHaveBeenCalled();
database.collections([collection]);
expect(onTableEntitiesClick).toHaveBeenCalled();
expect(collection.onTableEntitiesClick).toHaveBeenCalled();
});
it("string value should call onTableEntitiesClick", () => {
@@ -309,7 +297,7 @@ describe("OpenActions", () => {
database.collections([collection]);
handleOpenAction(action, [database], explorer);
expect(onTableEntitiesClick).toHaveBeenCalled();
expect(collection.onTableEntitiesClick).toHaveBeenCalled();
});
it("enum value should call onTableEntitiesClick before collections are fetched", () => {
@@ -322,7 +310,7 @@ describe("OpenActions", () => {
database.collections([collection]);
handleOpenAction(action, [database], explorer);
expect(onTableEntitiesClick).toHaveBeenCalled();
expect(collection.onTableEntitiesClick).toHaveBeenCalled();
});
it("enum value should call onTableEntitiesClick", () => {
@@ -334,10 +322,10 @@ describe("OpenActions", () => {
};
handleOpenAction(action, [database], explorer);
expect(onTableEntitiesClick).not.toHaveBeenCalled();
expect(collection.onTableEntitiesClick).not.toHaveBeenCalled();
database.collections([collection]);
expect(onTableEntitiesClick).toHaveBeenCalled();
expect(collection.onTableEntitiesClick).toHaveBeenCalled();
});
});
@@ -351,10 +339,10 @@ describe("OpenActions", () => {
};
handleOpenAction(action, [database], explorer);
expect(onGraphDocumentsClick).not.toHaveBeenCalled();
expect(collection.onGraphDocumentsClick).not.toHaveBeenCalled();
database.collections([collection]);
expect(onGraphDocumentsClick).toHaveBeenCalled();
expect(collection.onGraphDocumentsClick).toHaveBeenCalled();
});
it("string value should call onGraphDocumentsClick", () => {
@@ -367,7 +355,7 @@ describe("OpenActions", () => {
database.collections([collection]);
handleOpenAction(action, [database], explorer);
expect(onGraphDocumentsClick).toHaveBeenCalled();
expect(collection.onGraphDocumentsClick).toHaveBeenCalled();
});
it("enum value should call onGraphDocumentsClick before collections are fetched", () => {
@@ -379,10 +367,10 @@ describe("OpenActions", () => {
};
handleOpenAction(action, [database], explorer);
expect(onGraphDocumentsClick).not.toHaveBeenCalled();
expect(collection.onGraphDocumentsClick).not.toHaveBeenCalled();
database.collections([collection]);
expect(onGraphDocumentsClick).toHaveBeenCalled();
expect(collection.onGraphDocumentsClick).toHaveBeenCalled();
});
it("enum value should call onGraphDocumentsClick", () => {
@@ -395,7 +383,7 @@ describe("OpenActions", () => {
database.collections([collection]);
handleOpenAction(action, [database], explorer);
expect(onGraphDocumentsClick).toHaveBeenCalled();
expect(collection.onGraphDocumentsClick).toHaveBeenCalled();
});
});
@@ -409,10 +397,10 @@ describe("OpenActions", () => {
};
handleOpenAction(action, [database], explorer);
expect(onNewQueryClick).not.toHaveBeenCalled();
expect(collection.onNewQueryClick).not.toHaveBeenCalled();
database.collections([collection]);
expect(onNewQueryClick).toHaveBeenCalled();
expect(collection.onNewQueryClick).toHaveBeenCalled();
});
it("string value should call onNewQueryClick", () => {
@@ -425,7 +413,7 @@ describe("OpenActions", () => {
database.collections([collection]);
handleOpenAction(action, [database], explorer);
expect(onNewQueryClick).toHaveBeenCalled();
expect(collection.onNewQueryClick).toHaveBeenCalled();
});
it("enum value should call onNewQueryClick before collections are fetched", () => {
@@ -437,10 +425,10 @@ describe("OpenActions", () => {
};
handleOpenAction(action, [database], explorer);
expect(onNewQueryClick).not.toHaveBeenCalled();
expect(collection.onNewQueryClick).not.toHaveBeenCalled();
database.collections([collection]);
expect(onNewQueryClick).toHaveBeenCalled();
expect(collection.onNewQueryClick).toHaveBeenCalled();
});
it("enum value should call onNewQueryClick", () => {
@@ -453,7 +441,7 @@ describe("OpenActions", () => {
database.collections([collection]);
handleOpenAction(action, [database], explorer);
expect(onNewQueryClick).toHaveBeenCalled();
expect(collection.onNewQueryClick).toHaveBeenCalled();
});
});
@@ -467,10 +455,10 @@ describe("OpenActions", () => {
};
handleOpenAction(action, [database], explorer);
expect(onSettingsClick).not.toHaveBeenCalled();
expect(collection.onSettingsClick).not.toHaveBeenCalled();
database.collections([collection]);
expect(onSettingsClick).toHaveBeenCalled();
expect(collection.onSettingsClick).toHaveBeenCalled();
});
it("string value should call onSettingsClick", () => {
@@ -483,7 +471,7 @@ describe("OpenActions", () => {
database.collections([collection]);
handleOpenAction(action, [database], explorer);
expect(onSettingsClick).toHaveBeenCalled();
expect(collection.onSettingsClick).toHaveBeenCalled();
});
it("enum value should call onSettingsClick before collections are fetched", () => {
@@ -495,10 +483,10 @@ describe("OpenActions", () => {
};
handleOpenAction(action, [database], explorer);
expect(onSettingsClick).not.toHaveBeenCalled();
expect(collection.onSettingsClick).not.toHaveBeenCalled();
database.collections([collection]);
expect(onSettingsClick).toHaveBeenCalled();
expect(collection.onSettingsClick).toHaveBeenCalled();
});
it("enum value should call onSettingsClick", () => {
@@ -511,7 +499,7 @@ describe("OpenActions", () => {
database.collections([collection]);
handleOpenAction(action, [database], explorer);
expect(onSettingsClick).toHaveBeenCalled();
expect(collection.onSettingsClick).toHaveBeenCalled();
});
});
});

View File

@@ -2,11 +2,12 @@
import * as ViewModels from "../Contracts/ViewModels";
import { ActionContracts } from "../Contracts/ExplorerContracts";
import Explorer from "./Explorer";
export function handleOpenAction(
action: ActionContracts.DataExplorerAction,
databases: ViewModels.Database[],
explorer: ViewModels.Explorer
explorer: Explorer
): boolean {
if (
action.actionType === ActionContracts.ActionType.OpenCollectionTab ||
@@ -126,7 +127,7 @@ function openCollectionTab(
}
}
function openPane(action: ActionContracts.OpenPane, explorer: ViewModels.Explorer) {
function openPane(action: ActionContracts.OpenPane, explorer: Explorer) {
if (
action.paneKind === ActionContracts.PaneKind.AddCollection ||
(<any>action).paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.AddCollection]
@@ -154,7 +155,7 @@ function openPane(action: ActionContracts.OpenPane, explorer: ViewModels.Explore
}
}
function openFile(action: ActionContracts.OpenSampleNotebook, explorer: ViewModels.Explorer) {
function openFile(action: ActionContracts.OpenSampleNotebook, explorer: Explorer) {
explorer.handleOpenFileAction(decodeURIComponent(action.path));
}

View File

@@ -1,850 +0,0 @@
import * as DataModels from "../../src/Contracts/DataModels";
import * as ko from "knockout";
import * as ViewModels from "../../src/Contracts/ViewModels";
import DocumentClientUtilityBase from "../Common/DocumentClientUtilityBase";
import Q from "q";
import { ArcadiaWorkspaceItem } from "./Controls/Arcadia/ArcadiaMenuPicker";
import { CassandraTableKey, CassandraTableKeys, TableDataClient } from "../../src/Explorer/Tables/TableDataClient";
import { ConsoleData } from "../../src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { MostRecentActivity } from "./MostRecentActivity/MostRecentActivity";
import { NotebookContentItem } from "./Notebook/NotebookContentItem";
import { PlatformType } from "../../src/PlatformType";
import { QuerySelectPane } from "../../src/Explorer/Panes/Tables/QuerySelectPane";
import { SetupNotebooksPane } from "./Panes/SetupNotebooksPane";
import { Splitter } from "../../src/Common/Splitter";
import { StringInputPane } from "./Panes/StringInputPane";
import { TableColumnOptionsPane } from "../../src/Explorer/Panes/Tables/TableColumnOptionsPane";
import { TextFieldProps } from "./Controls/DialogReactComponent/DialogComponent";
import { UploadDetails } from "../workers/upload/definitions";
import { UploadFilePane } from "./Panes/UploadFilePane";
import { UploadItemsPaneAdapter } from "./Panes/UploadItemsPaneAdapter";
import { Versions } from "../../src/Contracts/ExplorerContracts";
import { CollectionCreationDefaults } from "../Shared/Constants";
import { IGalleryItem } from "../Juno/JunoClient";
import { ReactAdapter } from "../Bindings/ReactBindingHandler";
import { TabsManager } from "./Tabs/TabsManager";
export class ExplorerStub implements ViewModels.Explorer {
public flight: ko.Observable<string>;
public addCollectionText: ko.Observable<string>;
public hasAutoPilotV2FeatureFlag: ko.Computed<boolean>;
public addDatabaseText: ko.Observable<string>;
public collectionTitle: ko.Observable<string>;
public deleteCollectionText: ko.Observable<string>;
public deleteDatabaseText: ko.Observable<string>;
public collectionTreeNodeAltText: ko.Observable<string>;
public refreshTreeTitle: ko.Observable<string>;
public collapsedResourceTreeWidth: number;
public collectionCreationDefaults: ViewModels.CollectionCreationDefaults = CollectionCreationDefaults;
public hasWriteAccess: ko.Observable<boolean> = ko.observable<boolean>(false);
public databaseAccount: ko.Observable<ViewModels.DatabaseAccount>;
public subscriptionType: ko.Observable<ViewModels.SubscriptionType>;
public quotaId: ko.Observable<string>;
public defaultExperience: ko.Observable<string>;
public isPreferredApiDocumentDB: ko.Computed<boolean>;
public isPreferredApiCassandra: ko.Computed<boolean>;
public isPreferredApiMongoDB: ko.Computed<boolean>;
public isPreferredApiGraph: ko.Computed<boolean>;
public isPreferredApiTable: ko.Computed<boolean>;
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
public isEmulator: boolean;
public isAccountReady: ko.Observable<boolean>;
public canSaveQueries: ko.Computed<boolean>;
public features: ko.Observable<any>;
public serverId: ko.Observable<string>;
public extensionEndpoint: ko.Observable<string> = ko.observable<string>(undefined);
public armEndpoint: ko.Observable<string>;
public isTryCosmosDBSubscription: ko.Observable<boolean>;
public documentClientUtility: DocumentClientUtilityBase;
public notificationsClient: ViewModels.NotificationsClient;
public queriesClient: ViewModels.QueriesClient;
public tableDataClient: TableDataClient;
public splitter: Splitter;
public notificationConsoleData: ko.ObservableArray<ConsoleData>;
public isNotificationConsoleExpanded: ko.Observable<boolean>;
public contextPanes: ViewModels.ContextualPane[];
public databases: ko.ObservableArray<ViewModels.Database>;
public nonSystemDatabases: ko.Computed<ViewModels.Database[]>;
public selectedDatabaseId: ko.Computed<string>;
public selectedCollectionId: ko.Computed<string>;
public isLeftPaneExpanded: ko.Observable<boolean>;
public selectedNode: ko.Observable<ViewModels.TreeNode>;
public isRefreshingExplorer: ko.Observable<boolean>;
public isTabsContentExpanded: ko.Observable<boolean>;
public addCollectionPane: ViewModels.AddCollectionPane;
public addDatabasePane: ViewModels.AddDatabasePane;
public deleteCollectionConfirmationPane: ViewModels.DeleteCollectionConfirmationPane;
public deleteDatabaseConfirmationPane: ViewModels.DeleteDatabaseConfirmationPane;
public graphStylingPane: ViewModels.GraphStylingPane;
public addTableEntityPane: ViewModels.AddTableEntityPane;
public editTableEntityPane: ViewModels.EditTableEntityPane;
public tableColumnOptionsPane: TableColumnOptionsPane;
public querySelectPane: QuerySelectPane;
public newVertexPane: ViewModels.NewVertexPane;
public cassandraAddCollectionPane: ViewModels.CassandraAddCollectionPane;
public renewAdHocAccessPane: ViewModels.RenewAdHocAccessPane;
public renewExplorerShareAccess: (explorer: ViewModels.Explorer, token: string) => Q.Promise<void>;
public settingsPane: ViewModels.SettingsPane;
public executeSprocParamsPane: ViewModels.ExecuteSprocParamsPane;
public uploadItemsPane: ViewModels.UploadItemsPane;
public uploadItemsPaneAdapter: UploadItemsPaneAdapter;
public loadQueryPane: ViewModels.LoadQueryPane;
public saveQueryPane: ViewModels.ContextualPane;
public browseQueriesPane: ViewModels.BrowseQueriesPane;
public uploadFilePane: UploadFilePane;
public stringInputPane: StringInputPane;
public setupNotebooksPane: SetupNotebooksPane;
public isGalleryPublishEnabled: ko.Computed<boolean>;
public isGitHubPaneEnabled: ko.Observable<boolean>;
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
public isRightPanelV2Enabled: ko.Computed<boolean>;
public canExceedMaximumValue: ko.Computed<boolean>;
public isHostedDataExplorerEnabled: ko.Computed<boolean>;
public parentFrameDataExplorerVersion: ko.Observable<string> = ko.observable<string>(Versions.DataExplorer);
public mostRecentActivity: MostRecentActivity;
public isNotebookEnabled: ko.Observable<boolean>;
public isSparkEnabled: ko.Observable<boolean>;
public isNotebooksEnabledForAccount: ko.Observable<boolean>;
public isSparkEnabledForAccount: ko.Observable<boolean>;
public arcadiaToken: ko.Observable<string>;
public notebookWorkspaceManager: ViewModels.NotebookWorkspaceManager;
public sparkClusterManager: ViewModels.SparkClusterManager;
public notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>;
public sparkClusterConnectionInfo: ko.Observable<DataModels.SparkClusterConnectionInfo>;
public libraryManagePane: ViewModels.ContextualPane;
public clusterLibraryPane: ViewModels.ContextualPane;
public gitHubReposPane: ViewModels.ContextualPane;
public publishNotebookPaneAdapter: ReactAdapter;
public arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>;
public hasStorageAnalyticsAfecFeature: ko.Observable<boolean>;
public isSynapseLinkUpdating: ko.Observable<boolean>;
public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
public notebookManager?: any;
public openGallery: (notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean) => void;
public openNotebookViewer: (notebookUrl: string) => void;
public resourceTokenDatabaseId: ko.Observable<string>;
public resourceTokenCollectionId: ko.Observable<string>;
public resourceTokenCollection: ko.Observable<ViewModels.CollectionBase>;
public resourceTokenPartitionKey: ko.Observable<string>;
public isAuthWithResourceToken: ko.Observable<boolean>;
public isResourceTokenCollectionNodeSelected: ko.Computed<boolean>;
public tabsManager: TabsManager;
private _featureEnabledReturnValue: boolean;
constructor(options?: any) {
options = options || {};
this._featureEnabledReturnValue = options.featureEnabledReturnValue || false;
this.isSynapseLinkUpdating = ko.observable<boolean>(options.isSynapseLinkUpdating || false);
}
public openEnableSynapseLinkDialog() {
throw new Error("Not implemented");
}
public createWorkspace(): Promise<string> {
throw new Error("Not implemented");
}
public createSparkPool(workspaceId: string): Promise<string> {
throw new Error("Not implemented");
}
public isDatabaseNodeOrNoneSelected(): boolean {
throw new Error("Not implemented");
}
public isDatabaseNodeSelected(): boolean {
throw new Error("Not implemented");
}
public isNodeKindSelected(nodeKind: string): boolean {
throw new Error("Not implemented");
}
public isNoneSelected(): boolean {
throw new Error("Not implemented");
}
public isFeatureEnabled(feature: string): boolean {
return this._featureEnabledReturnValue;
}
public isSelectedDatabaseShared(): boolean {
throw new Error("Not implemented");
}
public logConsoleData(consoleData: ConsoleData): void {
throw new Error("Not implemented");
}
public deleteInProgressConsoleDataWithId(id: string): void {
throw new Error("Not implemented");
}
public toggleLeftPaneExpanded() {
throw new Error("Not implemented");
}
public refreshAllDatabases(): Q.Promise<any> {
throw new Error("Not implemented");
}
public refreshDatabaseForResourceToken(): Q.Promise<void> {
throw new Error("Note impplemented");
}
public onRefreshDatabasesKeyPress = (source: any, event: KeyboardEvent): boolean => {
throw new Error("Not implemented");
};
public onRefreshResourcesClick = (source: any, event: MouseEvent): boolean => {
throw new Error("Not implemented");
};
public toggleLeftPaneExpandedKeyPress = (source: any, event: KeyboardEvent): boolean => {
throw new Error("Not implemented");
};
// Facade
public provideFeedbackEmail = () => {
throw new Error("Not implemented");
};
public handleMessage(event: MessageEvent) {
throw new Error("Not implemented");
}
public findSelectedDatabase(): ViewModels.Database {
throw new Error("Not implemented");
}
public findDatabaseWithId(databaseId: string): ViewModels.Database {
throw new Error("Not implemented");
}
public isLastDatabase(): boolean {
throw new Error("Not implemented");
}
public isLastNonEmptyDatabase(): boolean {
throw new Error("Not implemented");
}
public initDataExplorerWithFrameInputs(inputs: ViewModels.DataExplorerInputsFrame): Q.Promise<void> {
throw new Error("Not implemented");
}
public findSelectedCollection(): ViewModels.Collection {
throw new Error("Not implemented");
}
public findCollection(rid: string): ViewModels.Collection {
throw new Error("Not implemented");
}
public isLastCollection(): boolean {
throw new Error("Not implemented");
}
public findSelectedStoredProcedure(): ViewModels.StoredProcedure {
throw new Error("Not implemented");
}
public findSelectedUDF(): ViewModels.UserDefinedFunction {
throw new Error("Not implemented");
}
public findSelectedTrigger(): ViewModels.Trigger {
throw new Error("Not implemented");
}
public generateSharedAccessData(): void {
throw new Error("Not implemented");
}
public displayConnectExplorerForm(): void {
throw new Error("Not implemented");
}
public displayContextSwitchPromptForConnectionString(connectionString: string): void {
throw new Error("Not implemented");
}
public hideConnectExplorerForm(): void {
throw new Error("Not implemented");
}
public displayGuestAccessTokenRenewalPrompt(): void {
throw new Error("Not implemented");
}
public expandConsole(): void {
throw new Error("Not implemented");
}
public collapseConsole(): void {
throw new Error("Not implemented");
}
public rebindDocumentClientUtility(documentClientUtility: any) {
throw new Error("Not implemented");
}
public renewShareAccess(token: string): Q.Promise<void> {
throw new Error("Not implemented");
}
public getPlatformType(): PlatformType {
throw new Error("Not implemented");
}
public isRunningOnNationalCloud(): boolean {
throw new Error("Not implemented");
}
public isConnectExplorerVisible(): boolean {
return false;
}
public closeAllPanes(): void {
// return for now so tests dont break
// TODO: implement once we start testing pane close
return;
}
public onUpdateTabsButtons(buttons: ViewModels.NavbarButtonConfig[]): void {
throw new Error("Not implemented");
}
public importAndOpen(path: string): Promise<boolean> {
throw new Error("Not implemented");
}
public importAndOpenContent(name: string, content: string): Promise<boolean> {
throw new Error("Not implemented");
}
public publishNotebook(name: string, content: string): void {
throw new Error("Not implemented");
}
public async openNotebook(notebookContentItem: NotebookContentItem): Promise<boolean> {
throw new Error("Not implemented");
}
public deleteNotebookFile(item: NotebookContentItem): Promise<void> {
throw new Error("Not implemented");
}
public onCreateDirectory(parent: NotebookContentItem): Q.Promise<NotebookContentItem> {
throw new Error("Not implemented");
}
public onNewNotebookClicked(parent?: NotebookContentItem): void {
throw new Error("Not implemented");
}
public openNotebookTerminal(): void {
throw new Error("Not implemented");
}
public resetNotebookWorkspace(): void {
throw new Error("Not implemented");
}
public onNewCollectionClicked(): void {
throw new Error("Not implemented");
}
public onUploadToNotebookServerClicked(parent?: NotebookContentItem): void {
throw new Error("Not implemented");
}
public renameNotebook(notebookFile: NotebookContentItem): Q.Promise<NotebookContentItem> {
throw new Error("Not implemented");
}
public readFile(notebookFile: NotebookContentItem): Promise<string> {
throw new Error("Not implemented");
}
public downloadFile(notebookFile: NotebookContentItem): Promise<void> {
throw new Error("Not implemented");
}
public initNotebooks(databaseAccount: DataModels.DatabaseAccount): Promise<void> {
throw new Error("Not implemented");
}
public showOkModalDialog(title: string, msg: string): void {
throw new Error("Not implemented");
}
public showOkCancelModalDialog(
title: string,
msg: string,
okLabel: string,
onOk: () => void,
cancelLabel: string,
onCancel: () => void
): void {
throw new Error("Not implemented");
}
public showOkCancelTextFieldModalDialog(
title: string,
msg: string,
okLabel: string,
onOk: () => void,
cancelLabel: string,
onCancel: () => void,
textFieldProps: TextFieldProps,
isPrimaryButtonDisabled?: boolean
): void {
throw new Error("Not implemented");
}
public deleteCluster(): void {
throw new Error("Not implemented");
}
public async openSparkMasterTab(): Promise<void> {
throw new Error("Not implemented");
}
public createNotebookContentItemFile(name: string, filepath: string): NotebookContentItem {
throw new Error("Not implemented");
}
public refreshContentItem(item: NotebookContentItem): Promise<void> {
throw new Error("Not implemented");
}
public getNotebookBasePath(): string {
throw new Error("Not implemented");
}
public handleOpenFileAction(): Promise<void> {
throw new Error("Not implemented");
}
}
export class DatabaseStub implements ViewModels.Database {
public nodeKind: string;
public container: ViewModels.Explorer;
public self: string;
public rid: string;
public id: ko.Observable<string>;
public collections: ko.ObservableArray<ViewModels.Collection>;
public isDatabaseExpanded: ko.Observable<boolean>;
public isDatabaseShared: ko.Computed<boolean>;
public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
public offer: ko.Observable<DataModels.Offer>;
constructor(options?: any) {
this.nodeKind = options.nodeKind;
this.container = options.container;
this.self = options.self;
this.rid = options.rid;
this.id = options.id;
this.collections = options.collections;
this.isDatabaseExpanded = options.isDatabaseExpanded;
this.offer = options.offer;
this.selectedSubnodeKind = options.selectedSubnodeKind;
}
public onKeyPress = (source: any, event: KeyboardEvent): boolean => {
throw new Error("Not implemented");
};
public onKeyDown = (source: any, event: KeyboardEvent): boolean => {
throw new Error("Not implemented");
};
public onMenuKeyDown = (source: any, event: KeyboardEvent): boolean => {
throw new Error("Not implemented");
};
public onDeleteDatabaseContextMenuClick(source: ViewModels.Database, event: MouseEvent | KeyboardEvent) {
throw new Error("Not implemented");
}
public selectDatabase() {
throw new Error("Not implemented");
}
public expandCollapseDatabase() {
throw new Error("Not implemented");
}
public expandDatabase() {
throw new Error("Not implemented");
}
public collapseDatabase() {
throw new Error("Not implemented");
}
public loadCollections(): Q.Promise<void> {
throw new Error("Not implemented");
}
public findCollectionWithId(collectionId: string): ViewModels.Collection {
throw new Error("Not implemented");
}
public openAddCollection(database: ViewModels.Database, event: MouseEvent) {
throw new Error("Not implemented");
}
public readSettings() {
throw new Error("Not implemented");
}
public onSettingsClick(): void {
throw new Error("Not implemented");
}
}
export class CollectionStub implements ViewModels.Collection {
public nodeKind: string;
public container: ViewModels.Explorer;
public rawDataModel: DataModels.Collection;
public self: string;
public rid: string;
public databaseId: string;
public partitionKey: DataModels.PartitionKey;
public partitionKeyPropertyHeader: string;
public partitionKeyProperty: string;
public id: ko.Observable<string>;
public defaultTtl: ko.Observable<number>;
public analyticalStorageTtl: ko.Observable<number>;
public indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
public uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
public quotaInfo: ko.Observable<DataModels.CollectionQuotaInfo>;
public offer: ko.Observable<DataModels.Offer>;
public partitions: ko.Computed<number>;
public throughput: ko.Computed<number>;
public cassandraKeys: CassandraTableKeys;
public cassandraSchema: CassandraTableKey[];
public documentIds: ko.ObservableArray<ViewModels.DocumentId>;
public children: ko.ObservableArray<ViewModels.TreeNode>;
public storedProcedures: ko.Computed<ViewModels.StoredProcedure[]>;
public userDefinedFunctions: ko.Computed<ViewModels.UserDefinedFunction[]>;
public triggers: ko.Computed<ViewModels.Trigger[]>;
public showStoredProcedures: ko.Observable<boolean>;
public showTriggers: ko.Observable<boolean>;
public showUserDefinedFunctions: ko.Observable<boolean>;
public selectedDocumentContent: ViewModels.Editable<any>;
public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
public focusedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
public isCollectionExpanded: ko.Observable<boolean>;
public isStoredProceduresExpanded: ko.Observable<boolean>;
public isUserDefinedFunctionsExpanded: ko.Observable<boolean>;
public isTriggersExpanded: ko.Observable<boolean>;
public documentsFocused: ko.Observable<boolean>;
public settingsFocused: ko.Observable<boolean>;
public storedProceduresFocused: ko.Observable<boolean>;
public userDefinedFunctionsFocused: ko.Observable<boolean>;
public triggersFocused: ko.Observable<boolean>;
public conflictResolutionPolicy: ko.Observable<DataModels.ConflictResolutionPolicy>;
public changeFeedPolicy: ko.Observable<DataModels.ChangeFeedPolicy>;
public geospatialConfig: ko.Observable<DataModels.GeospatialConfig>;
constructor(options: any) {
this.nodeKind = options.nodeKind;
this.container = options.container;
this.self = options.self;
this.rid = options.rid;
this.databaseId = options.databaseId;
this.partitionKey = options.partitionKey;
this.partitionKeyPropertyHeader = options.partitionKeyPropertyHeader;
this.partitionKeyProperty = options.partitionKeyProperty;
this.id = options.id;
this.defaultTtl = options.defaultTtl;
this.analyticalStorageTtl = options.analyticalStorageTtl;
this.indexingPolicy = options.indexingPolicy;
this.uniqueKeyPolicy = options.uniqueKeyPolicy;
this.quotaInfo = options.quotaInfo;
this.offer = options.offer;
this.partitions = options.partitions;
this.throughput = options.throughput;
this.cassandraKeys = options.cassandraKeys;
this.cassandraSchema = options.cassandraSchema;
this.documentIds = options.documentIds;
this.children = options.children;
this.storedProcedures = options.storedProcedures;
this.userDefinedFunctions = options.userDefinedFunctions;
this.triggers = options.triggers;
this.showStoredProcedures = options.showStoredProcedures;
this.showTriggers = options.showTriggers;
this.showUserDefinedFunctions = options.showUserDefinedFunctions;
this.selectedDocumentContent = options.selectedDocumentContent;
this.selectedSubnodeKind = options.selectedSubnodeKind;
this.focusedSubnodeKind = options.focusedSubnodeKind;
this.isCollectionExpanded = options.isCollectionExpanded;
this.isStoredProceduresExpanded = options.isStoredProceduresExpanded;
this.isUserDefinedFunctionsExpanded = options.isUserDefinedFunctionsExpanded;
this.isTriggersExpanded = options.isTriggersExpanded;
this.documentsFocused = options.documentsFocused;
this.settingsFocused = options.settingsFocused;
this.storedProceduresFocused = options.storedProceduresFocused;
this.userDefinedFunctionsFocused = options.userDefinedFunctionsFocused;
this.triggersFocused = options.triggersFocused;
}
public expandCollapseCollection() {
throw new Error("Not implemented");
}
public collapseCollection() {
throw new Error("Not implemented");
}
public expandCollection(): Q.Promise<void> {
throw new Error("Not implemented");
}
public onDocumentDBDocumentsClick() {
throw new Error("onDocumentDBDocumentsClick");
}
public onTableEntitiesClick() {
throw new Error("Not implemented");
}
public onGraphDocumentsClick() {
throw new Error("Not implemented");
}
public onMongoDBDocumentsClick = () => {
throw new Error("Not implemented");
};
public openTab = () => {
throw new Error("Not implemented");
};
public onSettingsClick() {
throw new Error("Not implemented");
}
public onConflictsClick() {
throw new Error("Not implemented");
}
public readSettings(): Q.Promise<void> {
throw new Error("Not implemented");
}
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
throw new Error("Not implemented");
}
public onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string) {
throw new Error("Not implemented");
}
public onNewGraphClick() {
throw new Error("Not implemented");
}
public onNewMongoShellClick() {
throw new Error("Not implemented");
}
public onNewStoredProcedureClick(source: ViewModels.Collection, event: MouseEvent) {
throw new Error("Not implemented");
}
public onNewUserDefinedFunctionClick(source: ViewModels.Collection, event: MouseEvent) {
throw new Error("Not implemented");
}
public onNewTriggerClick(source: ViewModels.Collection, event: MouseEvent) {
throw new Error("Not implemented");
}
public createStoredProcedureNode(data: DataModels.StoredProcedure): ViewModels.StoredProcedure {
throw new Error("Not implemented");
}
public createUserDefinedFunctionNode(data: DataModels.UserDefinedFunction): ViewModels.UserDefinedFunction {
throw new Error("Not implemented");
}
public createTriggerNode(data: DataModels.Trigger): ViewModels.Trigger {
throw new Error("Not implemented");
}
public expandCollapseStoredProcedures() {
throw new Error("Not implemented");
}
public expandStoredProcedures() {
throw new Error("Not implemented");
}
public collapseStoredProcedures() {
throw new Error("Not implemented");
}
public expandCollapseUserDefinedFunctions() {
throw new Error("Not implemented");
}
public expandUserDefinedFunctions() {
throw new Error("Not implemented");
}
public collapseUserDefinedFunctions() {
throw new Error("Not implemented");
}
public expandCollapseTriggers() {
throw new Error("Not implemented");
}
public expandTriggers() {
throw new Error("Not implemented");
}
public collapseTriggers() {
throw new Error("Not implemented");
}
public loadStoredProcedures(): Q.Promise<any> {
throw new Error("Not implemented");
}
public loadUserDefinedFunctions(): Q.Promise<any> {
throw new Error("Not implemented");
}
public loadTriggers(): Q.Promise<any> {
throw new Error("Not implemented");
}
public onDragOver(source: ViewModels.Collection, event: { originalEvent: DragEvent }) {
throw new Error("Not implemented");
}
public onDrop(source: ViewModels.Collection, event: { originalEvent: DragEvent }) {
throw new Error("Not implemented");
}
public isCollectionNodeSelected(): boolean {
throw new Error("Not implemented");
}
public isSubNodeSelected(nodeKind: ViewModels.CollectionTabKind): boolean {
throw new Error("Not implemented");
}
public onDeleteCollectionContextMenuClick(source: ViewModels.Collection, event: MouseEvent | KeyboardEvent) {
throw new Error("Not implemented");
}
public findStoredProcedureWithId(sprocId: string): ViewModels.StoredProcedure {
throw new Error("Not implemented");
}
public findTriggerWithId(triggerId: string): ViewModels.Trigger {
throw new Error("Not implemented");
}
public findUserDefinedFunctionWithId(userDefinedFunctionId: string): ViewModels.UserDefinedFunction {
throw new Error("Not implemented");
}
public uploadFiles = (fileList: FileList): Q.Promise<UploadDetails> => {
throw new Error("Not implemented");
};
public getLabel(): string {
throw new Error("Not implemented");
}
public getDatabase(): ViewModels.Database {
throw new Error("Not implemented");
}
}
class ContextualPaneStub implements ViewModels.ContextualPane {
public documentClientUtility: DocumentClientUtilityBase;
public formErrors: ko.Observable<string>;
public formErrorsDetails: ko.Observable<string>;
public id: string;
public title: ko.Observable<string>;
public visible: ko.Observable<boolean>;
public firstFieldHasFocus: ko.Observable<boolean>;
public isExecuting: ko.Observable<boolean>;
public submit() {
throw new Error("Not implemented");
}
public cancel() {
throw new Error("Not implemented");
}
public open() {
throw new Error("Not implemented");
}
public close() {
throw new Error("Not implemented");
}
public hideErrorDetails() {
throw new Error("Not implemented");
}
public resetData() {
throw new Error("Not implemented");
}
public showErrorDetails() {
throw new Error("Not implemented");
}
public onCloseKeyPress(source: any, event: KeyboardEvent): void {
throw new Error("Not implemented");
}
public onPaneKeyDown(source: any, event: KeyboardEvent): boolean {
throw new Error("Not implemented");
}
}
export class AddCollectionPaneStub extends ContextualPaneStub implements ViewModels.AddCollectionPane {
public collectionIdTitle: ko.Observable<string>;
public databaseId: ko.Observable<string>;
public partitionKey: ko.Observable<string>;
public storage: ko.Observable<string>;
public throughputSinglePartition: ko.Observable<number>;
public throughputMultiPartition: ko.Observable<number>;
public collectionMaxSharedThroughputTitle: ko.Observable<string>;
public collectionWithThroughputInSharedTitle: ko.Observable<string>;
public onEnableSynapseLinkButtonClicked() {
throw new Error("Not implemented");
}
public onStorageOptionsKeyDown(source: any, event: KeyboardEvent): boolean {
throw new Error("Not implemented");
}
public onRupmOptionsKeyDown(source: any, event: KeyboardEvent): void {
throw new Error("Not implemented");
}
}
export class AddDatabasePaneStub extends ContextualPaneStub implements ViewModels.AddDatabasePane {}
export class CassandraAddCollectionPane extends ContextualPaneStub implements ViewModels.CassandraAddCollectionPane {
public createTableQuery: ko.Observable<string>;
public keyspaceId: ko.Observable<string>;
public userTableQuery: ko.Observable<string>;
}

View File

@@ -7,7 +7,7 @@ import { AutopilotTier } from "../../Contracts/DataModels";
describe("Add Collection Pane", () => {
describe("isValid()", () => {
let explorer: ViewModels.Explorer;
let explorer: Explorer;
const mockDatabaseAccount: ViewModels.DatabaseAccount = {
id: "mock",
kind: "DocumentDB",

View File

@@ -21,7 +21,7 @@ import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
import { HashMap } from "../../Common/HashMap";
import { PlatformType } from "../../PlatformType";
export default class AddCollectionPane extends ContextualPaneBase implements ViewModels.AddCollectionPane {
export default class AddCollectionPane extends ContextualPaneBase {
public defaultExperience: ko.Computed<string>;
public databaseIds: ko.ObservableArray<string>;
public collectionId: ko.Observable<string>;

View File

@@ -5,7 +5,7 @@ import AddDatabasePane from "./AddDatabasePane";
describe("Add Database Pane", () => {
describe("getSharedThroughputDefault()", () => {
let explorer: ViewModels.Explorer;
let explorer: Explorer;
const mockDatabaseAccount: ViewModels.DatabaseAccount = {
id: "mock",
kind: "DocumentDB",

View File

@@ -17,7 +17,7 @@ import { ContextualPaneBase } from "./ContextualPaneBase";
import { CosmosClient } from "../../Common/CosmosClient";
import { PlatformType } from "../../PlatformType";
export default class AddDatabasePane extends ContextualPaneBase implements ViewModels.AddDatabasePane {
export default class AddDatabasePane extends ContextualPaneBase {
public defaultExperience: ko.Computed<string>;
public databaseIdLabel: ko.Computed<string>;
public databaseId: ko.Observable<string>;

View File

@@ -7,7 +7,7 @@ import * as Logger from "../../Common/Logger";
import { QueriesGridComponentAdapter } from "../Controls/QueriesGridReactComponent/QueriesGridComponentAdapter";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
export class BrowseQueriesPane extends ContextualPaneBase implements ViewModels.BrowseQueriesPane {
export class BrowseQueriesPane extends ContextualPaneBase {
public queriesGridComponentAdapter: QueriesGridComponentAdapter;
public canSaveQueries: ko.Computed<boolean>;

View File

@@ -13,8 +13,7 @@ import { CassandraAPIDataClient } from "../Tables/TableDataClient";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { HashMap } from "../../Common/HashMap";
export default class CassandraAddCollectionPane extends ContextualPaneBase
implements ViewModels.CassandraAddCollectionPane {
export default class CassandraAddCollectionPane extends ContextualPaneBase {
public createTableQuery: ko.Observable<string>;
public keyspaceId: ko.Observable<string>;
public maxThroughputRU: ko.Observable<number>;

View File

@@ -1,59 +0,0 @@
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
<div class="contextual-pane-out" data-bind="click: cancel, clickBubble: false"></div>
<div class="contextual-pane" id="clusterLibraryPane">
<!-- Cluster Library -- Start -->
<div class="contextual-pane-in">
<form class="paneContentContainer" data-bind="submit: submit">
<!-- Cluster Library header - Start -->
<div class="firstdivbg headerline">
<span data-bind="text: title"></span>
<div
class="closeImg"
role="button"
aria-label="Close pane"
tabindex="0"
data-bind="click: cancel, event: { keypress: onCloseKeyPress }"
>
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
</div>
</div>
<!-- Cluster Library header - End -->
<!-- Cluster Library errors - Start -->
<div
class="warningErrorContainer"
aria-live="assertive"
data-bind="visible: formErrors() && formErrors() !== ''"
>
<div class="warningErrorContent">
<span><img class="paneErrorIcon" src="/error_red.svg" alt="Error"/></span>
<span class="warningErrorDetailsLinkContainer">
<span class="formErrors" data-bind="text: formErrors, attr: { title: formErrors }"></span>
<a
class="errorLink"
role="link"
data-bind="visible: formErrorsDetails() && formErrorsDetails() !== '', click: showErrorDetails"
>More details</a
>
</span>
</div>
</div>
<!-- Cluster Library errors - End -->
<!-- Cluster Library inputs - Start -->
<div class="paneMainContent"><div data-bind="react: clusterLibraryGridAdapter"></div></div>
<!-- Cluster Library inputs - End -->
<div class="paneFooter">
<div class="leftpanel-okbut"><input type="submit" value="Save" class="btncreatecoll1" /></div>
</div>
</form>
</div>
<!-- Cluster Library - End -->
<!-- Loader - Start -->
<div class="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" data-bind="visible: isExecuting">
<img class="dataExplorerLoader" src="/LoadingIndicator_3Squares.gif" />
</div>
<!-- Loader - End -->
</div>
</div>

View File

@@ -1,237 +0,0 @@
import _ from "underscore";
import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { ClusterLibraryGridAdapter } from "../Controls/LibraryManagement/ClusterLibraryGridAdapter";
import { ClusterLibraryGridProps, ClusterLibraryItem } from "../Controls/LibraryManagement/ClusterLibraryGrid";
import { Library, SparkCluster, SparkClusterLibrary } from "../../Contracts/DataModels";
import * as Logger from "../../Common/Logger";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
export class ClusterLibraryPane extends ContextualPaneBase {
public clusterLibraryGridAdapter: ClusterLibraryGridAdapter;
private _clusterLibraryProps: ko.Observable<ClusterLibraryGridProps>;
private _originalCluster: SparkCluster;
constructor(options: ViewModels.PaneOptions) {
super(options);
this.title("Cluster Libraries");
this._clusterLibraryProps = ko.observable<ClusterLibraryGridProps>({
libraryItems: [],
onInstalledChanged: this._onInstalledChanged
});
this.clusterLibraryGridAdapter = new ClusterLibraryGridAdapter();
this.clusterLibraryGridAdapter.parameters = this._clusterLibraryProps;
this.resetData();
}
public open(): void {
const resourceId: string = this.container.databaseAccount() && this.container.databaseAccount().id;
Promise.all([this._getLibraries(resourceId), this._getDefaultCluster(resourceId)]).then(
result => {
const [libraries, cluster] = result;
this._originalCluster = cluster;
const libraryItems = this._mapClusterLibraries(cluster, libraries);
this._updateClusterLibraryGridStates({ libraryItems });
},
reason => {
const parsedError = ErrorParserUtility.parse(reason);
this.formErrors(parsedError[0].message);
}
);
super.open();
}
public submit(): void {
const resourceId: string = this.container.databaseAccount() && this.container.databaseAccount().id;
this.isExecuting(true);
if (this._areLibrariesChanged()) {
const newLibraries = this._clusterLibraryProps()
.libraryItems.filter(lib => lib.installed)
.map(lib => ({ name: lib.name }));
this._updateClusterLibraries(resourceId, this._originalCluster, newLibraries).then(
() => {
this.isExecuting(false);
this.close();
},
reason => {
this.isExecuting(false);
const parsedError = ErrorParserUtility.parse(reason);
this.formErrors(parsedError[0].message);
}
);
} else {
this.isExecuting(false);
this.close();
}
}
private _updateClusterLibraryGridStates(states: Partial<ClusterLibraryGridProps>): void {
const merged = { ...this._clusterLibraryProps(), ...states };
this._clusterLibraryProps(merged);
this._clusterLibraryProps.valueHasMutated();
}
private _onInstalledChanged = (libraryName: string, installed: boolean): void => {
const items = this._clusterLibraryProps().libraryItems;
const library = _.find(items, item => item.name === libraryName);
library.installed = installed;
this._clusterLibraryProps.valueHasMutated();
};
private _areLibrariesChanged(): boolean {
const original = this._originalCluster.properties && this._originalCluster.properties.libraries;
const changed = this._clusterLibraryProps()
.libraryItems.filter(lib => lib.installed)
.map(lib => lib.name);
if (original.length !== changed.length) {
return true;
}
const newLibraries = new Set(changed);
for (let o of original) {
if (!newLibraries.has(o.name)) {
return false;
}
newLibraries.delete(o.name);
}
return newLibraries.size === 0;
}
private _mapClusterLibraries(cluster: SparkCluster, libraries: Library[]): ClusterLibraryItem[] {
const clusterLibraries = cluster && cluster.properties && cluster.properties.libraries;
const libraryItems = libraries.map(lib => ({
...lib,
installed: clusterLibraries.some(clusterLib => clusterLib.name === lib.name)
}));
return libraryItems;
}
private async _getLibraries(resourceId: string): Promise<Library[]> {
if (!resourceId) {
return Promise.reject("invalid inputs");
}
if (!this.container.sparkClusterManager) {
return Promise.reject("cluster client is not initialized yet");
}
const inProgressId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Fetching libraries...`
);
try {
return await this.container.sparkClusterManager.getLibrariesAsync(resourceId);
} catch (e) {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to fetch libraries. Reason: ${JSON.stringify(e)}`
);
Logger.logError(e, "Explorer/_getLibraries");
throw e;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(inProgressId);
}
}
private async _getDefaultCluster(resourceId: string, clusterId: string = "default"): Promise<SparkCluster> {
if (!resourceId) {
return Promise.reject("invalid inputs");
}
if (!this.container.sparkClusterManager) {
return Promise.reject("cluster client is not initialized yet");
}
const inProgressId = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, `Fetching cluster...`);
try {
const cluster = await this.container.sparkClusterManager.getClusterAsync(resourceId, clusterId);
return cluster;
} catch (e) {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to fetch cluster. Reason: ${JSON.stringify(e)}`
);
Logger.logError(e, "Explorer/_getCluster");
throw e;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(inProgressId);
}
}
private async _updateClusterLibraries(
resourceId: string,
originalCluster: SparkCluster,
newLibrarys: SparkClusterLibrary[]
): Promise<void> {
if (!originalCluster || !resourceId) {
return Promise.reject("Invalid inputs");
}
if (!this.container.sparkClusterManager) {
return Promise.reject("Cluster client is not initialized yet");
}
TelemetryProcessor.traceStart(Action.ClusterLibraryManage, {
resourceId,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(),
area: "ClusterLibraryPane/_updateClusterLibraries",
originalCluster,
newLibrarys
});
let newCluster = originalCluster;
newCluster.properties.libraries = newLibrarys;
const consoleId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Updating ${newCluster.name} libraries...`
);
try {
const cluster = await this.container.sparkClusterManager.updateClusterAsync(
resourceId,
originalCluster.name,
newCluster
);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully updated ${newCluster.name} libraries.`
);
TelemetryProcessor.traceSuccess(Action.ClusterLibraryManage, {
resourceId,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(),
area: "ClusterLibraryPane/_updateClusterLibraries",
cluster
});
} catch (e) {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to upload ${newCluster.name} libraries. Reason: ${JSON.stringify(e)}`
);
TelemetryProcessor.traceFailure(Action.ClusterLibraryManage, {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(),
area: "ClusterLibraryPane/_updateClusterLibraries",
error: e
});
Logger.logError(e, "Explorer/_updateClusterLibraries");
throw e;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(consoleId);
}
}
}

View File

@@ -1,5 +1,4 @@
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import * as Constants from "../../Common/Constants";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
@@ -7,11 +6,12 @@ import { KeyCodes } from "../../Common/Constants";
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import DocumentClientUtilityBase from "../../Common/DocumentClientUtilityBase";
import Explorer from "../Explorer";
// TODO: Use specific actions for logging telemetry data
export abstract class ContextualPaneBase extends WaitsForTemplateViewModel implements ViewModels.ContextualPane {
public id: string;
public container: ViewModels.Explorer;
public container: Explorer;
public firstFieldHasFocus: ko.Observable<boolean>;
public formErrorsDetails: ko.Observable<string>;
public formErrors: ko.Observable<string>;

View File

@@ -9,45 +9,44 @@ import DeleteFeedback from "../../Common/DeleteFeedback";
import DocumentClientUtilityBase from "../../Common/DocumentClientUtilityBase";
import Explorer from "../Explorer";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { CollectionStub, DatabaseStub, ExplorerStub } from "../OpenActionsStubs";
import { TreeNode } from "../../Contracts/ViewModels";
describe("Delete Collection Confirmation Pane", () => {
describe("Explorer.isLastCollection()", () => {
let explorer: ViewModels.Explorer;
let explorer: Explorer;
beforeEach(() => {
explorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false });
});
it("should be true if 1 database and 1 collection", () => {
let database: ViewModels.Database = new DatabaseStub({});
database.collections = ko.observableArray<ViewModels.Collection>([new CollectionStub({})]);
let database = {} as ViewModels.Database;
database.collections = ko.observableArray<ViewModels.Collection>([{} as ViewModels.Collection]);
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
expect(explorer.isLastCollection()).toBe(true);
});
it("should be false if if 1 database and 2 collection", () => {
let database: ViewModels.Database = new DatabaseStub({});
let database = {} as ViewModels.Database;
database.collections = ko.observableArray<ViewModels.Collection>([
new CollectionStub({}),
new CollectionStub({})
{} as ViewModels.Collection,
{} as ViewModels.Collection
]);
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
expect(explorer.isLastCollection()).toBe(false);
});
it("should be false if 2 database and 1 collection each", () => {
let database: ViewModels.Database = new DatabaseStub({});
database.collections = ko.observableArray<ViewModels.Collection>([new CollectionStub({})]);
let database2: ViewModels.Database = new DatabaseStub({});
database2.collections = ko.observableArray<ViewModels.Collection>([new CollectionStub({})]);
let database = {} as ViewModels.Database;
database.collections = ko.observableArray<ViewModels.Collection>([{} as ViewModels.Collection]);
let database2 = {} as ViewModels.Database;
database2.collections = ko.observableArray<ViewModels.Collection>([{} as ViewModels.Collection]);
explorer.databases = ko.observableArray<ViewModels.Database>([database, database2]);
expect(explorer.isLastCollection()).toBe(false);
});
it("should be false if 0 databases", () => {
let database: ViewModels.Database = new DatabaseStub({});
let database = {} as ViewModels.Database;
explorer.databases = ko.observableArray<ViewModels.Database>();
database.collections = ko.observableArray<ViewModels.Collection>();
expect(explorer.isLastCollection()).toBe(false);
@@ -59,29 +58,27 @@ describe("Delete Collection Confirmation Pane", () => {
let fakeDocumentClientUtility = sinon.createStubInstance<DocumentClientUtilityBase>(
DocumentClientUtilityBase as any
);
let fakeExplorer = sinon.createStubInstance<ExplorerStub>(ExplorerStub as any);
sinon.stub(fakeExplorer, "isNotificationConsoleExpanded").value(ko.observable<boolean>(false));
let fakeExplorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false });
fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
fakeExplorer.refreshAllDatabases = () => Q.resolve();
let pane = new DeleteCollectionConfirmationPane({
documentClientUtility: fakeDocumentClientUtility as any,
id: "deletecollectionconfirmationpane",
visible: ko.observable<boolean>(false),
container: fakeExplorer as any
container: fakeExplorer
});
fakeExplorer.isLastCollection.returns(true);
fakeExplorer.isSelectedDatabaseShared.returns(false);
pane.container = fakeExplorer as any;
fakeExplorer.isLastCollection = () => true;
fakeExplorer.isSelectedDatabaseShared = () => false;
expect(pane.shouldRecordFeedback()).toBe(true);
fakeExplorer.isLastCollection.returns(true);
fakeExplorer.isSelectedDatabaseShared.returns(true);
pane.container = fakeExplorer as any;
fakeExplorer.isLastCollection = () => true;
fakeExplorer.isSelectedDatabaseShared = () => true;
expect(pane.shouldRecordFeedback()).toBe(false);
fakeExplorer.isLastCollection.returns(false);
fakeExplorer.isSelectedDatabaseShared.returns(false);
pane.container = fakeExplorer as any;
fakeExplorer.isLastCollection = () => false;
fakeExplorer.isSelectedDatabaseShared = () => false;
expect(pane.shouldRecordFeedback()).toBe(false);
});
});
@@ -99,38 +96,35 @@ describe("Delete Collection Confirmation Pane", () => {
it("it should log feedback if last collection and database is not shared", () => {
let selectedCollectionId = "testCol";
let fakeDocumentClientUtility = sinon.createStubInstance<DocumentClientUtilityBase>(
DocumentClientUtilityBase as any
);
fakeDocumentClientUtility.deleteCollection.returns(Q.resolve(null));
let fakeExplorer = sinon.createStubInstance<ExplorerStub>(ExplorerStub as any);
fakeExplorer.findSelectedCollection.returns(
new CollectionStub({
let fakeDocumentClientUtility = {} as DocumentClientUtilityBase;
fakeDocumentClientUtility.deleteCollection = () => Q(null);
let fakeExplorer = {} as Explorer;
fakeExplorer.findSelectedCollection = () => {
return {
id: ko.observable<string>(selectedCollectionId),
rid: "test"
})
);
sinon.stub(fakeExplorer, "isNotificationConsoleExpanded").value(ko.observable<boolean>(false));
sinon.stub(fakeExplorer, "selectedCollectionId").value(ko.observable<string>(selectedCollectionId));
fakeExplorer.isSelectedDatabaseShared.returns(false);
} as ViewModels.Collection;
};
fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
fakeExplorer.selectedCollectionId = ko.computed<string>(() => selectedCollectionId);
fakeExplorer.isSelectedDatabaseShared = () => false;
const SubscriptionId = "testId";
const AccountName = "testAccount";
sinon.stub(fakeExplorer, "databaseAccount").value(
ko.observable<ViewModels.DatabaseAccount>({
id: SubscriptionId,
name: AccountName
} as ViewModels.DatabaseAccount)
);
sinon.stub(fakeExplorer, "defaultExperience").value(ko.observable<string>("DocumentDB"));
sinon.stub(fakeExplorer, "isPreferredApiCassandra").value(
ko.computed(() => {
return false;
})
);
sinon.stub(fakeExplorer, "documentClientUtility").value(fakeDocumentClientUtility);
sinon.stub(fakeExplorer, "selectedNode").value(ko.observable<TreeNode>());
fakeExplorer.isLastCollection.returns(true);
fakeExplorer.isSelectedDatabaseShared.returns(false);
fakeExplorer.databaseAccount = ko.observable<ViewModels.DatabaseAccount>({
id: SubscriptionId,
name: AccountName
} as ViewModels.DatabaseAccount);
fakeExplorer.defaultExperience = ko.observable<string>("DocumentDB");
fakeExplorer.isPreferredApiCassandra = ko.computed(() => {
return false;
});
fakeExplorer.documentClientUtility = fakeDocumentClientUtility;
fakeExplorer.selectedNode = ko.observable<TreeNode>();
fakeExplorer.isLastCollection = () => true;
fakeExplorer.isSelectedDatabaseShared = () => false;
fakeExplorer.refreshAllDatabases = () => Q.resolve();
let pane = new DeleteCollectionConfirmationPane({
documentClientUtility: fakeDocumentClientUtility as any,

View File

@@ -12,8 +12,7 @@ import DeleteFeedback from "../../Common/DeleteFeedback";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
export default class DeleteCollectionConfirmationPane extends ContextualPaneBase
implements ViewModels.DeleteCollectionConfirmationPane {
export default class DeleteCollectionConfirmationPane extends ContextualPaneBase {
public collectionIdConfirmationText: ko.Observable<string>;
public collectionIdConfirmation: ko.Observable<string>;
public containerDeleteFeedback: ko.Observable<string>;

View File

@@ -8,41 +8,40 @@ import DeleteDatabaseConfirmationPane from "./DeleteDatabaseConfirmationPane";
import DeleteFeedback from "../../Common/DeleteFeedback";
import DocumentClientUtilityBase from "../../Common/DocumentClientUtilityBase";
import Explorer from "../Explorer";
import { CollectionStub, DatabaseStub, ExplorerStub } from "../OpenActionsStubs";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { TreeNode } from "../../Contracts/ViewModels";
import { TabsManager } from "../Tabs/TabsManager";
describe("Delete Database Confirmation Pane", () => {
describe("Explorer.isLastDatabase() and Explorer.isLastNonEmptyDatabase()", () => {
let explorer: ViewModels.Explorer;
let explorer: Explorer;
beforeEach(() => {
explorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false });
});
it("should be true if only 1 database", () => {
let database: ViewModels.Database = new DatabaseStub({});
let database = {} as ViewModels.Database;
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
expect(explorer.isLastDatabase()).toBe(true);
});
it("should be false if only 2 databases", () => {
let database: ViewModels.Database = new DatabaseStub({});
let database2: ViewModels.Database = new DatabaseStub({});
let database = {} as ViewModels.Database;
let database2 = {} as ViewModels.Database;
explorer.databases = ko.observableArray<ViewModels.Database>([database, database2]);
expect(explorer.isLastDatabase()).toBe(false);
});
it("should be false if not last empty database", () => {
let database: ViewModels.Database = new DatabaseStub({});
let database = {} as ViewModels.Database;
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
expect(explorer.isLastNonEmptyDatabase()).toBe(false);
});
it("should be true if last non empty database", () => {
let database: ViewModels.Database = new DatabaseStub({});
database.collections = ko.observableArray<ViewModels.Collection>([new CollectionStub({})]);
let database = {} as ViewModels.Database;
database.collections = ko.observableArray<ViewModels.Collection>([{} as ViewModels.Collection]);
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
expect(explorer.isLastNonEmptyDatabase()).toBe(true);
});
@@ -50,11 +49,9 @@ describe("Delete Database Confirmation Pane", () => {
describe("shouldRecordFeedback()", () => {
it("should return true if last non empty database or is last database that has shared throughput, else false", () => {
let fakeDocumentClientUtility = sinon.createStubInstance<DocumentClientUtilityBase>(
DocumentClientUtilityBase as any
);
let fakeExplorer = sinon.createStubInstance<ExplorerStub>(ExplorerStub as any);
sinon.stub(fakeExplorer, "isNotificationConsoleExpanded").value(ko.observable<boolean>(false));
let fakeDocumentClientUtility = {} as DocumentClientUtilityBase;
let fakeExplorer = {} as Explorer;
fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
let pane = new DeleteDatabaseConfirmationPane({
documentClientUtility: fakeDocumentClientUtility as any,
@@ -63,18 +60,18 @@ describe("Delete Database Confirmation Pane", () => {
container: fakeExplorer as any
});
fakeExplorer.isLastNonEmptyDatabase.returns(true);
fakeExplorer.isLastNonEmptyDatabase = () => true;
pane.container = fakeExplorer as any;
expect(pane.shouldRecordFeedback()).toBe(true);
fakeExplorer.isLastDatabase.returns(true);
fakeExplorer.isSelectedDatabaseShared.returns(true);
fakeExplorer.isLastDatabase = () => true;
fakeExplorer.isSelectedDatabaseShared = () => true;
pane.container = fakeExplorer as any;
expect(pane.shouldRecordFeedback()).toBe(true);
fakeExplorer.isLastNonEmptyDatabase.returns(false);
fakeExplorer.isLastDatabase.returns(true);
fakeExplorer.isSelectedDatabaseShared.returns(false);
fakeExplorer.isLastNonEmptyDatabase = () => false;
fakeExplorer.isLastDatabase = () => true;
fakeExplorer.isSelectedDatabaseShared = () => false;
pane.container = fakeExplorer as any;
expect(pane.shouldRecordFeedback()).toBe(false);
});
@@ -93,39 +90,34 @@ describe("Delete Database Confirmation Pane", () => {
it("on submit() it should log feedback if last non empty database or is last database that has shared throughput", () => {
let selectedDatabaseId = "testDB";
let fakeDocumentClientUtility = sinon.createStubInstance<DocumentClientUtilityBase>(
DocumentClientUtilityBase as any
);
fakeDocumentClientUtility.deleteDatabase.returns(Q.resolve(null));
let fakeExplorer = sinon.createStubInstance<ExplorerStub>(ExplorerStub as any);
fakeExplorer.findSelectedDatabase.returns(
new DatabaseStub({
let fakeDocumentClientUtility = {} as DocumentClientUtilityBase;
fakeDocumentClientUtility.deleteDatabase = () => Q.resolve(null);
let fakeExplorer = {} as Explorer;
fakeExplorer.findSelectedDatabase = () => {
return {
id: ko.observable<string>(selectedDatabaseId),
rid: "test",
collections: ko.observableArray<ViewModels.Collection>()
})
);
sinon.stub(fakeExplorer, "isNotificationConsoleExpanded").value(ko.observable<boolean>(false));
sinon.stub(fakeExplorer, "selectedDatabaseId").value(ko.observable<string>(selectedDatabaseId));
fakeExplorer.isSelectedDatabaseShared.returns(false);
} as ViewModels.Database;
};
fakeExplorer.refreshAllDatabases = () => Q.resolve();
fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
fakeExplorer.selectedDatabaseId = ko.computed<string>(() => selectedDatabaseId);
fakeExplorer.isSelectedDatabaseShared = () => false;
const SubscriptionId = "testId";
const AccountName = "testAccount";
sinon.stub(fakeExplorer, "databaseAccount").value(
ko.observable<ViewModels.DatabaseAccount>({
id: SubscriptionId,
name: AccountName
} as ViewModels.DatabaseAccount)
);
sinon.stub(fakeExplorer, "defaultExperience").value(ko.observable<string>("DocumentDB"));
sinon.stub(fakeExplorer, "isPreferredApiCassandra").value(
ko.computed(() => {
return false;
})
);
sinon.stub(fakeExplorer, "documentClientUtility").value(fakeDocumentClientUtility);
sinon.stub(fakeExplorer, "selectedNode").value(ko.observable<TreeNode>());
sinon.stub(fakeExplorer, "tabsManager").value(new TabsManager());
fakeExplorer.isLastNonEmptyDatabase.returns(true);
fakeExplorer.databaseAccount = ko.observable<ViewModels.DatabaseAccount>({
id: SubscriptionId,
name: AccountName
} as ViewModels.DatabaseAccount);
fakeExplorer.defaultExperience = ko.observable<string>("DocumentDB");
fakeExplorer.isPreferredApiCassandra = ko.computed(() => {
return false;
});
fakeExplorer.documentClientUtility = fakeDocumentClientUtility;
fakeExplorer.selectedNode = ko.observable<TreeNode>();
fakeExplorer.tabsManager = new TabsManager();
fakeExplorer.isLastNonEmptyDatabase = () => true;
let pane = new DeleteDatabaseConfirmationPane({
documentClientUtility: fakeDocumentClientUtility as any,

View File

@@ -13,8 +13,7 @@ import DeleteFeedback from "../../Common/DeleteFeedback";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase
implements ViewModels.DeleteDatabaseConfirmationPane {
export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
public databaseIdConfirmationText: ko.Observable<string>;
public databaseIdConfirmation: ko.Observable<string>;
public databaseDeleteFeedback: ko.Observable<string>;

View File

@@ -3,6 +3,7 @@ import * as _ from "underscore";
import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import { ContextualPaneBase } from "./ContextualPaneBase";
import StoredProcedure from "../Tree/StoredProcedure";
export interface ExecuteSprocParam {
type: ko.Observable<string>;
@@ -14,7 +15,7 @@ type UnwrappedExecuteSprocParam = {
value: any;
};
export class ExecuteSprocParamsPane extends ContextualPaneBase implements ViewModels.ExecuteSprocParamsPane {
export class ExecuteSprocParamsPane extends ContextualPaneBase {
public params: ko.ObservableArray<ExecuteSprocParam>;
public partitionKeyType: ko.Observable<string>;
public partitionKeyValue: ko.Observable<string>;
@@ -22,7 +23,7 @@ export class ExecuteSprocParamsPane extends ContextualPaneBase implements ViewMo
public addNewParamLabel: string = "Add New Param";
public executeButtonEnabled: ko.Computed<boolean>;
private _selectedSproc: ViewModels.StoredProcedure;
private _selectedSproc: StoredProcedure;
constructor(options: ViewModels.PaneOptions) {
super(options);
@@ -39,8 +40,7 @@ export class ExecuteSprocParamsPane extends ContextualPaneBase implements ViewMo
public open() {
super.open();
const currentSelectedSproc: ViewModels.StoredProcedure =
this.container && this.container.findSelectedStoredProcedure();
const currentSelectedSproc = this.container && this.container.findSelectedStoredProcedure();
if (!!currentSelectedSproc && !!this._selectedSproc && this._selectedSproc.rid !== currentSelectedSproc.rid) {
this.params([]);
this.partitionKeyValue("");

View File

@@ -1,13 +1,13 @@
import * as React from "react";
import * as ViewModels from "../../Contracts/ViewModels";
import { IconButton, PrimaryButton } from "office-ui-fabric-react/lib/Button";
import { KeyCodes } from "../../Common/Constants";
import { Subscription } from "knockout";
import ErrorRedIcon from "../../../images/error_red.svg";
import LoadingIndicatorIcon from "../../../images/LoadingIndicator_3Squares.gif";
import Explorer from "../Explorer";
export interface GenericRightPaneProps {
container: ViewModels.Explorer;
container: Explorer;
content: JSX.Element;
formError: string;
formErrorDetail: string;

View File

@@ -2,7 +2,7 @@ import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import { ContextualPaneBase } from "./ContextualPaneBase";
export default class GraphStylingPane extends ContextualPaneBase implements ViewModels.GraphStylingPane {
export default class GraphStylingPane extends ContextualPaneBase {
public graphConfigUIData: ViewModels.GraphConfigUiData;
private remoteConfig: ViewModels.GraphConfigUiData;

View File

@@ -1,55 +0,0 @@
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
<div class="contextual-pane-out" data-bind="click: cancel, clickBubble: false"></div>
<div class="contextual-pane" id="libraryManagePane">
<!-- Library Manage -- Start -->
<div class="contextual-pane-in">
<form class="paneContentContainer" data-bind="submit: submit">
<!-- Library Manage header - Start -->
<div class="firstdivbg headerline">
<span data-bind="text: title"></span>
<div
class="closeImg"
role="button"
aria-label="Close pane"
tabindex="0"
data-bind="click: cancel, event: { keypress: onCloseKeyPress }"
>
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
</div>
</div>
<!-- Library Manage header - End -->
<!-- Library Manage errors - Start -->
<div
class="warningErrorContainer"
aria-live="assertive"
data-bind="visible: formErrors() && formErrors() !== ''"
>
<div class="warningErrorContent">
<span><img class="paneErrorIcon" src="/error_red.svg" alt="Error"/></span>
<span class="warningErrorDetailsLinkContainer">
<span class="formErrors" data-bind="text: formErrors, attr: { title: formErrors }"></span>
<a
class="errorLink"
role="link"
data-bind="visible: formErrorsDetails() && formErrorsDetails() !== '', click: showErrorDetails"
>More details</a
>
</span>
</div>
</div>
<!-- Library Manage errors - End -->
<!-- Library Manage inputs - Start -->
<div class="paneMainContent"><div data-bind="react: libraryManageComponentAdapter"></div></div>
<!-- Library Manage inputs - End -->
</form>
</div>
<!-- Library Manage - End -->
<!-- Loader - Start -->
<div class="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" data-bind="visible: isExecuting">
<img class="dataExplorerLoader" src="/LoadingIndicator_3Squares.gif" />
</div>
<!-- Loader - End -->
</div>
</div>

View File

@@ -1,372 +0,0 @@
import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { LibraryManageComponentAdapter } from "../Controls/LibraryManagement/LibraryManageComponentAdapter";
import {
LibraryManageComponentProps,
LibraryAddNameTextFieldProps,
LibraryAddUrlTextFieldProps,
LibraryAddButtonProps,
LibraryManageGridProps
} from "../Controls/LibraryManagement/LibraryManage";
import { Library } from "../../Contracts/DataModels";
import * as Logger from "../../Common/Logger";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
export class LibraryManagePane extends ContextualPaneBase {
public libraryManageComponentAdapter: LibraryManageComponentAdapter;
private _libraryManageProps: ko.Observable<LibraryManageComponentProps>;
private _libraryManageStates: { isNameValid: boolean; isUrlValid: boolean };
constructor(options: ViewModels.PaneOptions) {
super(options);
this.title("Libraries");
this._libraryManageStates = {
isNameValid: true,
isUrlValid: true
};
this._libraryManageProps = ko.observable<LibraryManageComponentProps>({
addProps: {
nameProps: {
libraryName: "",
onLibraryNameChange: this._onLibraryNameChange,
onLibraryNameValidated: this._onLibraryNameValidated
},
urlProps: {
libraryAddress: "",
onLibraryAddressChange: this._onLibraryAddressChange,
onLibraryAddressValidated: this._onLibraryAddressValidated
},
buttonProps: {
disabled: false,
onLibraryAddClick: this._onLibraryAddClick
}
},
gridProps: {
items: [],
onLibraryDeleteClick: this._onLibraryDeleteClick
}
});
this.libraryManageComponentAdapter = new LibraryManageComponentAdapter();
this.libraryManageComponentAdapter.parameters = this._libraryManageProps;
this.resetData();
}
public open(): void {
const resourceId: string = this.container.databaseAccount() && this.container.databaseAccount().id;
this._getLibraries(resourceId).then(
(libraries: Library[]) => {
this._updateLibraryManageComponentProps(null, null, null, {
items: libraries
});
},
reason => {
const parsedError = ErrorParserUtility.parse(reason);
this.formErrors(parsedError[0].message);
}
);
super.open();
}
public submit(): void {
// override default behavior because this is not a form
}
private _updateLibraryManageComponentProps(
newNameProps?: Partial<LibraryAddNameTextFieldProps>,
newUrlProps?: Partial<LibraryAddUrlTextFieldProps>,
newButtonProps?: Partial<LibraryAddButtonProps>,
newGridProps?: Partial<LibraryManageGridProps>
): void {
let {
addProps: { buttonProps, nameProps, urlProps },
gridProps
} = this._libraryManageProps();
if (newNameProps) {
nameProps = { ...nameProps, ...newNameProps };
}
if (newUrlProps) {
urlProps = { ...urlProps, ...newUrlProps };
}
if (newButtonProps) {
buttonProps = { ...buttonProps, ...newButtonProps };
}
if (newGridProps) {
gridProps = { ...gridProps, ...newGridProps };
}
this._libraryManageProps({
addProps: {
nameProps,
urlProps,
buttonProps
},
gridProps
});
this._libraryManageProps.valueHasMutated();
}
private _onLibraryNameChange = (libraryName: string): void => {
this._updateLibraryManageComponentProps({ libraryName });
};
private _onLibraryNameValidated = (errorMessage: string): void => {
this._libraryManageStates.isNameValid = !errorMessage;
this._validateAddButton();
};
private _onLibraryAddressChange = (libraryAddress: string): void => {
this._updateLibraryManageComponentProps(null, {
libraryAddress
});
if (!this._libraryManageProps().addProps.nameProps.libraryName) {
const parsedLibraryAddress = this._parseLibraryUrl(libraryAddress);
if (!parsedLibraryAddress) {
return;
}
let libraryName = this._sanitizeLibraryName(parsedLibraryAddress[2]);
this._updateLibraryManageComponentProps({ libraryName });
}
};
private _sanitizeLibraryName = (libraryName: string): string => {
const invalidCharRegex = /[^a-zA-Z0-9-]/gm;
return libraryName
.replace(invalidCharRegex, "-")
.substring(0, Math.min(Constants.SparkLibrary.nameMaxLength, libraryName.length));
};
private _onLibraryAddressValidated = (errorMessage: string): void => {
this._libraryManageStates.isUrlValid = !errorMessage;
this._validateAddButton();
};
private _validateAddButton = (): void => {
const isValid = this._libraryManageStates.isNameValid && this._libraryManageStates.isUrlValid;
const isUploadDisabled = this._libraryManageProps().addProps.buttonProps.disabled;
if (isValid === isUploadDisabled) {
this._updateLibraryManageComponentProps(null, null, {
disabled: !isUploadDisabled
});
}
};
private _onLibraryDeleteClick = (libraryName: string): void => {
const resourceId: string = this.container.databaseAccount() && this.container.databaseAccount().id;
this.isExecuting(true);
this._deleteLibrary(resourceId, libraryName).then(
() => {
this.isExecuting(false);
const items = this._libraryManageProps().gridProps.items.filter(lib => lib.name !== libraryName);
this._updateLibraryManageComponentProps(null, null, null, {
items
});
},
reason => {
this.isExecuting(false);
const parsedError = ErrorParserUtility.parse(reason);
this.formErrors(parsedError[0].message);
}
);
};
private _onLibraryAddClick = (): void => {
const libraryAddress = this._libraryManageProps().addProps.urlProps.libraryAddress;
if (!libraryAddress) {
this.formErrors("Library Url cannot be null");
return;
}
const libraryName = this._libraryManageProps().addProps.nameProps.libraryName || this._generateLibraryName();
if (!libraryName) {
this.formErrors("Library Name cannot be null");
return;
}
const parsedLibraryAddress = this._parseLibraryUrl(libraryAddress);
if (!parsedLibraryAddress) {
return;
}
const library: Library = {
name: libraryName,
properties: {
kind: "Jar",
source: {
kind: "HttpsUri",
libraryFileName: `${libraryName}.${parsedLibraryAddress[3]}`,
uri: libraryAddress
}
}
};
const resourceId: string = this.container.databaseAccount() && this.container.databaseAccount().id;
this.isExecuting(true);
this._updateLibraryManageComponentProps(null, null, { disabled: true });
this._addLibrary(resourceId, library).then(
() => {
this.isExecuting(false);
this._updateLibraryManageComponentProps(
{
libraryName: ""
},
{
libraryAddress: ""
},
{
disabled: false
},
{
items: [...this._libraryManageProps().gridProps.items, library]
}
);
},
reason => {
this.isExecuting(false);
const parsedError = ErrorParserUtility.parse(reason);
this.formErrors(parsedError[0].message);
}
);
};
private _parseLibraryUrl = (url: string): RegExpExecArray => {
const libraryUrlRegex = /^(https:\/\/.+\/)(.+)\.(jar)$/gi;
return libraryUrlRegex.exec(url);
};
private _generateLibraryName = (): string => {
return `library-${Math.random()
.toString(32)
.substring(2)}`;
};
private async _getLibraries(resourceId: string): Promise<Library[]> {
if (!resourceId) {
return Promise.reject("Invalid inputs");
}
if (!this.container.sparkClusterManager) {
return Promise.reject("Cluster client is not initialized yet");
}
const inProgressId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Fetching libraries...`
);
try {
const libraries = await this.container.sparkClusterManager.getLibrariesAsync(resourceId);
return libraries;
} catch (e) {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to fetch libraries. Reason: ${JSON.stringify(e)}`
);
Logger.logError(e, "Explorer/_getLibraries");
throw e;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(inProgressId);
}
}
private async _addLibrary(resourceId: string, library: Library): Promise<void> {
if (!library || !resourceId) {
return Promise.reject("invalid inputs");
}
if (!this.container.sparkClusterManager) {
return Promise.reject("cluster client is not initialized yet");
}
TelemetryProcessor.traceStart(Action.LibraryManage, {
resourceId,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(),
area: "LibraryManagePane/_deleteLibrary",
libraryName: library.name
});
const libraryName = library.name;
const inProgressId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Uploading ${libraryName}...`
);
try {
await this.container.sparkClusterManager.addLibraryAsync(resourceId, libraryName, library);
TelemetryProcessor.traceSuccess(Action.LibraryManage, {
resourceId,
area: "LibraryManagePane/_deleteLibrary",
libraryName: library.name
});
} catch (e) {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to upload ${libraryName}. Reason: ${JSON.stringify(e)}`
);
TelemetryProcessor.traceFailure(Action.LibraryManage, {
resourceId,
area: "LibraryManagePane/_deleteLibrary",
libraryName: library.name,
error: e
});
Logger.logError(e, "Explorer/_uploadLibrary");
throw e;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(inProgressId);
}
}
private async _deleteLibrary(resourceId: string, libraryName: string): Promise<void> {
if (!libraryName || !resourceId) {
return Promise.reject("invalid inputs");
}
if (!this.container.sparkClusterManager) {
return Promise.reject("cluster client is not initialized yet");
}
TelemetryProcessor.traceStart(Action.LibraryManage, {
resourceId,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(),
area: "LibraryManagePane/_deleteLibrary",
libraryName
});
const inProgressId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Deleting ${libraryName}...`
);
try {
await this.container.sparkClusterManager.deleteLibraryAsync(resourceId, libraryName);
TelemetryProcessor.traceSuccess(Action.LibraryManage, {
resourceId,
area: "LibraryManagePane/_deleteLibrary",
libraryName
});
} catch (e) {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to delete ${libraryName}. Reason: ${JSON.stringify(e)}`
);
TelemetryProcessor.traceFailure(Action.LibraryManage, {
resourceId,
area: "LibraryManagePane/_deleteLibrary",
libraryName,
error: e
});
Logger.logError(e, "Explorer/_deleteLibrary");
throw e;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(inProgressId);
}
}
}

View File

@@ -7,7 +7,7 @@ import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsol
import * as Logger from "../../Common/Logger";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
export class LoadQueryPane extends ContextualPaneBase implements ViewModels.LoadQueryPane {
export class LoadQueryPane extends ContextualPaneBase {
public selectedFilesTitle: ko.Observable<string>;
public files: ko.Observable<FileList>;

View File

@@ -2,9 +2,10 @@ import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { KeyCodes } from "../../Common/Constants";
import Explorer from "../Explorer";
export default class NewVertexPane extends ContextualPaneBase implements ViewModels.NewVertexPane {
public container: ViewModels.Explorer;
export default class NewVertexPane extends ContextualPaneBase {
public container: Explorer;
public visible: ko.Observable<boolean>;
public formErrors: ko.Observable<string>;
public formErrorsDetails: ko.Observable<string>;

View File

@@ -19,8 +19,6 @@ import BrowseQueriesPaneTemplate from "./BrowseQueriesPane.html";
import UploadFilePaneTemplate from "./UploadFilePane.html";
import StringInputPaneTemplate from "./StringInputPane.html";
import SetupNotebooksPaneTemplate from "./SetupNotebooksPane.html";
import LibraryManagePaneTemplate from "./LibraryManagePane.html";
import ClusterLibraryPaneTemplate from "./ClusterLibraryPane.html";
import GitHubReposPaneTemplate from "./GitHubReposPane.html";
export class PaneComponent {
@@ -218,24 +216,6 @@ export class SetupNotebooksPaneComponent {
}
}
export class LibraryManagePaneComponent {
constructor() {
return {
viewModel: PaneComponent,
template: LibraryManagePaneTemplate
};
}
}
export class ClusterLibraryPaneComponent {
constructor() {
return {
viewModel: PaneComponent,
template: ClusterLibraryPaneTemplate
};
}
}
export class GitHubReposPaneComponent {
constructor() {
return {

View File

@@ -1,164 +1,156 @@
import ko from "knockout";
import { ITextFieldProps, Stack, Text, TextField } from "office-ui-fabric-react";
import * as React from "react";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import * as Logger from "../../Common/Logger";
import * as ViewModels from "../../Contracts/ViewModels";
import { JunoClient } from "../../Juno/JunoClient";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { FileSystemUtil } from "../Notebook/FileSystemUtil";
import { GenericRightPaneComponent, GenericRightPaneProps } from "./GenericRightPaneComponent";
export class PublishNotebookPaneAdapter implements ReactAdapter {
parameters: ko.Observable<number>;
private isOpened: boolean;
private isExecuting: boolean;
private formError: string;
private formErrorDetail: string;
private name: string;
private author: string;
private content: string;
private description: string;
private tags: string;
private thumbnailUrl: string;
constructor(private container: ViewModels.Explorer, private junoClient: JunoClient) {
this.parameters = ko.observable(Date.now());
this.reset();
this.triggerRender();
}
public renderComponent(): JSX.Element {
if (!this.isOpened) {
return undefined;
}
const props: GenericRightPaneProps = {
container: this.container,
content: this.createContent(),
formError: this.formError,
formErrorDetail: this.formErrorDetail,
id: "publishnotebookpane",
isExecuting: this.isExecuting,
title: "Publish to gallery",
submitButtonText: "Publish",
onClose: () => this.close(),
onSubmit: () => this.submit()
};
return <GenericRightPaneComponent {...props} />;
}
public triggerRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
public open(name: string, author: string, content: string): void {
this.name = name;
this.author = author;
this.content = content;
this.isOpened = true;
this.triggerRender();
}
public close(): void {
this.reset();
this.triggerRender();
}
public async submit(): Promise<void> {
const notificationId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Publishing ${this.name} to gallery`
);
this.isExecuting = true;
this.triggerRender();
try {
if (!this.name || !this.description || !this.author) {
throw new Error("Name, description, and author are required");
}
const response = await this.junoClient.publishNotebook(
this.name,
this.description,
this.tags?.split(","),
this.author,
this.thumbnailUrl,
this.content
);
if (!response.data) {
throw new Error(`Received HTTP ${response.status} when publishing ${name} to gallery`);
}
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Published ${name} to gallery`);
} catch (error) {
this.formError = `Failed to publish ${this.name} to gallery`;
this.formErrorDetail = `${error}`;
const message = `${this.formError}: ${this.formErrorDetail}`;
Logger.logError(message, "PublishNotebookPaneAdapter/submit");
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
return;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
this.isExecuting = false;
this.triggerRender();
}
this.close();
}
private createContent = (): JSX.Element => {
const descriptionPara1 =
"This notebook has your data. Please make sure you delete any sensitive data/output before publishing.";
const descriptionPara2 = `Would you like to publish and share ${FileSystemUtil.stripExtension(
this.name,
"ipynb"
)} to the gallery?`;
const descriptionProps: ITextFieldProps = {
label: "Description",
ariaLabel: "Description",
multiline: true,
rows: 3,
required: true,
onChange: (event, newValue) => (this.description = newValue)
};
const tagsProps: ITextFieldProps = {
label: "Tags",
ariaLabel: "Tags",
placeholder: "Optional tag 1, Optional tag 2",
onChange: (event, newValue) => (this.tags = newValue)
};
const thumbnailProps: ITextFieldProps = {
label: "Cover image url",
ariaLabel: "Cover image url",
onChange: (event, newValue) => (this.thumbnailUrl = newValue)
};
return (
<div className="panelContent">
<Stack className="paneMainContent" tokens={{ childrenGap: 20 }}>
<Text>{descriptionPara1}</Text>
<Text>{descriptionPara2}</Text>
<TextField {...descriptionProps} />
<TextField {...tagsProps} />
<TextField {...thumbnailProps} />
</Stack>
</div>
);
};
private reset = (): void => {
this.isOpened = false;
this.isExecuting = false;
this.formError = undefined;
this.formErrorDetail = undefined;
this.name = undefined;
this.author = undefined;
this.content = undefined;
};
}
import ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import * as Logger from "../../Common/Logger";
import { JunoClient } from "../../Juno/JunoClient";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { GenericRightPaneComponent, GenericRightPaneProps } from "./GenericRightPaneComponent";
import Explorer from "../Explorer";
import { PublishNotebookPaneComponent, PublishNotebookPaneProps } from "./PublishNotebookPaneComponent";
export class PublishNotebookPaneAdapter implements ReactAdapter {
parameters: ko.Observable<number>;
private isOpened: boolean;
private isExecuting: boolean;
private formError: string;
private formErrorDetail: string;
private name: string;
private author: string;
private content: string;
private description: string;
private tags: string;
private imageSrc: string;
constructor(private container: Explorer, private junoClient: JunoClient) {
this.parameters = ko.observable(Date.now());
this.reset();
this.triggerRender();
}
public renderComponent(): JSX.Element {
if (!this.isOpened) {
return undefined;
}
const props: GenericRightPaneProps = {
container: this.container,
content: this.createContent(),
formError: this.formError,
formErrorDetail: this.formErrorDetail,
id: "publishnotebookpane",
isExecuting: this.isExecuting,
title: "Publish to gallery",
submitButtonText: "Publish",
onClose: () => this.close(),
onSubmit: () => this.submit()
};
return <GenericRightPaneComponent {...props} />;
}
public triggerRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
public open(name: string, author: string, content: string): void {
this.name = name;
this.author = author;
this.content = content;
this.isOpened = true;
this.triggerRender();
}
public close(): void {
this.reset();
this.triggerRender();
}
public async submit(): Promise<void> {
const notificationId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Publishing ${this.name} to gallery`
);
this.isExecuting = true;
this.triggerRender();
try {
if (!this.name || !this.description || !this.author) {
throw new Error("Name, description, and author are required");
}
const response = await this.junoClient.publishNotebook(
this.name,
this.description,
this.tags?.split(","),
this.author,
this.imageSrc,
this.content
);
if (!response.data) {
throw new Error(`Received HTTP ${response.status} when publishing ${name} to gallery`);
}
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Published ${name} to gallery`);
} catch (error) {
this.formError = `Failed to publish ${this.name} to gallery`;
this.formErrorDetail = `${error}`;
const message = `${this.formError}: ${this.formErrorDetail}`;
Logger.logError(message, "PublishNotebookPaneAdapter/submit");
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
return;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
this.isExecuting = false;
this.triggerRender();
}
this.close();
}
private createFormErrorForLargeImageSelection = (formError: string, formErrorDetail: string, area: string): void => {
this.formError = formError;
this.formErrorDetail = formErrorDetail;
const message = `${this.formError}: ${this.formErrorDetail}`;
Logger.logError(message, area);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
this.triggerRender();
};
private clearFormError = (): void => {
this.formError = undefined;
this.formErrorDetail = undefined;
this.triggerRender();
};
private createContent = (): JSX.Element => {
const publishNotebookPaneProps: PublishNotebookPaneProps = {
notebookName: this.name,
notebookDescription: "",
notebookTags: "",
notebookAuthor: this.author,
notebookCreatedDate: new Date().toISOString(),
onChangeDescription: (newValue: string) => (this.description = newValue),
onChangeTags: (newValue: string) => (this.tags = newValue),
onChangeImageSrc: (newValue: string) => (this.imageSrc = newValue),
onError: this.createFormErrorForLargeImageSelection,
clearFormError: this.clearFormError
};
return <PublishNotebookPaneComponent {...publishNotebookPaneProps} />;
};
private reset = (): void => {
this.isOpened = false;
this.isExecuting = false;
this.formError = undefined;
this.formErrorDetail = undefined;
this.name = undefined;
this.author = undefined;
this.content = undefined;
};
}

View File

@@ -0,0 +1,6 @@
.publishNotebookPanelContent {
display: flex;
flex-direction: column;
flex: 1;
overflow-y: auto;
}

View File

@@ -0,0 +1,23 @@
import { shallow } from "enzyme";
import React from "react";
import { PublishNotebookPaneComponent, PublishNotebookPaneProps } from "./PublishNotebookPaneComponent";
describe("PublishNotebookPaneComponent", () => {
it("renders", () => {
const props: PublishNotebookPaneProps = {
notebookName: "SampleNotebook.ipynb",
notebookDescription: "sample description",
notebookTags: "tag1, tag2",
notebookAuthor: "CosmosDB",
notebookCreatedDate: "2020-07-17T00:00:00Z",
onChangeDescription: undefined,
onChangeTags: undefined,
onChangeImageSrc: undefined,
onError: undefined,
clearFormError: undefined
};
const wrapper = shallow(<PublishNotebookPaneComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,205 @@
import { ITextFieldProps, Stack, Text, TextField, Dropdown, IDropdownProps } from "office-ui-fabric-react";
import * as React from "react";
import { GalleryCardComponent } from "../Controls/NotebookGallery/Cards/GalleryCardComponent";
import { FileSystemUtil } from "../Notebook/FileSystemUtil";
import "./PublishNotebookPaneComponent.less";
export interface PublishNotebookPaneProps {
notebookName: string;
notebookDescription: string;
notebookTags: string;
notebookAuthor: string;
notebookCreatedDate: string;
onChangeDescription: (newValue: string) => void;
onChangeTags: (newValue: string) => void;
onChangeImageSrc: (newValue: string) => void;
onError: (formError: string, formErrorDetail: string, area: string) => void;
clearFormError: () => void;
}
interface PublishNotebookPaneState {
type: string;
notebookDescription: string;
notebookTags: string;
imageSrc: string;
}
export class PublishNotebookPaneComponent extends React.Component<PublishNotebookPaneProps, PublishNotebookPaneState> {
private static readonly maxImageSizeInMib = 1.5;
private static readonly ImageTypes = ["URL", "Custom Image"];
private descriptionPara1: string;
private descriptionPara2: string;
private descriptionProps: ITextFieldProps;
private tagsProps: ITextFieldProps;
private thumbnailUrlProps: ITextFieldProps;
private thumbnailSelectorProps: IDropdownProps;
private imageToBase64: (file: File, updateImageSrc: (result: string) => void) => void;
constructor(props: PublishNotebookPaneProps) {
super(props);
this.state = {
type: PublishNotebookPaneComponent.ImageTypes[0],
notebookDescription: "",
notebookTags: "",
imageSrc: undefined
};
this.imageToBase64 = (file: File, updateImageSrc: (result: string) => void) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function() {
updateImageSrc(reader.result.toString());
};
const onError = this.props.onError;
reader.onerror = function(error) {
const formError = `Failed to convert ${file.name} to base64 format`;
const formErrorDetail = `${error}`;
const area = "PublishNotebookPaneComponent/selectImageFile";
onError(formError, formErrorDetail, area);
};
};
this.descriptionPara1 =
"This notebook has your data. Please make sure you delete any sensitive data/output before publishing.";
this.descriptionPara2 = `Would you like to publish and share "${FileSystemUtil.stripExtension(
this.props.notebookName,
"ipynb"
)}" to the gallery?`;
this.thumbnailUrlProps = {
label: "Cover image url",
ariaLabel: "Cover image url",
onChange: (event, newValue) => {
this.props.onChangeImageSrc(newValue);
this.setState({ imageSrc: newValue });
}
};
this.thumbnailSelectorProps = {
label: "Cover image",
defaultSelectedKey: PublishNotebookPaneComponent.ImageTypes[0],
ariaLabel: "Cover image",
options: PublishNotebookPaneComponent.ImageTypes.map((value: string) => ({ text: value, key: value })),
onChange: (event, options) => {
this.setState({ type: options.text });
}
};
this.descriptionProps = {
label: "Description",
ariaLabel: "Description",
multiline: true,
rows: 3,
required: true,
onChange: (event, newValue) => {
this.props.onChangeDescription(newValue);
this.setState({ notebookDescription: newValue });
}
};
this.tagsProps = {
label: "Tags",
ariaLabel: "Tags",
placeholder: "Optional tag 1, Optional tag 2",
onChange: (event, newValue) => {
this.props.onChangeTags(newValue);
this.setState({ notebookTags: newValue });
}
};
}
public render(): JSX.Element {
return (
<div className="publishNotebookPanelContent">
<Stack className="panelMainContent" tokens={{ childrenGap: 20 }}>
<Stack.Item>
<Text>{this.descriptionPara1}</Text>
</Stack.Item>
<Stack.Item>
<Text>{this.descriptionPara2}</Text>
</Stack.Item>
<Stack.Item>
<TextField {...this.descriptionProps} />
</Stack.Item>
<Stack.Item>
<TextField {...this.tagsProps} />
</Stack.Item>
<Stack.Item>
<Dropdown {...this.thumbnailSelectorProps} />
</Stack.Item>
{this.state.type === PublishNotebookPaneComponent.ImageTypes[0] ? (
<Stack.Item>
<TextField {...this.thumbnailUrlProps} />
</Stack.Item>
) : (
<Stack.Item>
<input
id="selectImageFile"
type="file"
accept="image/*"
onChange={event => {
const file = event.target.files[0];
if (file.size / 1024 ** 2 > PublishNotebookPaneComponent.maxImageSizeInMib) {
event.target.value = "";
const formError = `Failed to upload ${file.name}`;
const formErrorDetail = `Image is larger than ${PublishNotebookPaneComponent.maxImageSizeInMib} MiB. Please Choose a different image.`;
const area = "PublishNotebookPaneComponent/selectImageFile";
this.props.onError(formError, formErrorDetail, area);
this.props.onChangeImageSrc(undefined);
this.setState({ imageSrc: undefined });
return;
} else {
this.props.clearFormError();
}
this.imageToBase64(file, (result: string) => {
this.props.onChangeImageSrc(result);
this.setState({ imageSrc: result });
});
}}
/>
</Stack.Item>
)}
<Stack.Item>
<Text>Preview</Text>
</Stack.Item>
<Stack.Item>
<GalleryCardComponent
data={{
id: undefined,
name: this.props.notebookName,
description: this.state.notebookDescription,
gitSha: undefined,
tags: this.state.notebookTags.split(","),
author: this.props.notebookAuthor,
thumbnailUrl: this.state.imageSrc,
created: this.props.notebookCreatedDate,
isSample: false,
downloads: 0,
favorites: 0,
views: 0
}}
isFavorite={false}
showDownload={true}
showDelete={true}
onClick={undefined}
onTagClick={undefined}
onFavoriteClick={undefined}
onUnfavoriteClick={undefined}
onDownloadClick={undefined}
onDeleteClick={undefined}
/>
</Stack.Item>
</Stack>
</div>
);
}
}

View File

@@ -8,7 +8,7 @@ import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsol
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
export class RenewAdHocAccessPane extends ContextualPaneBase implements ViewModels.RenewAdHocAccessPane {
export class RenewAdHocAccessPane extends ContextualPaneBase {
public accessKey: ko.Observable<string>;
public isHelperImageVisible: ko.Observable<boolean>;

View File

@@ -4,7 +4,7 @@ import Explorer from "../Explorer";
describe("Settings Pane", () => {
describe("shouldShowQueryPageOptions()", () => {
let explorer: ViewModels.Explorer;
let explorer: Explorer;
beforeEach(() => {
explorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false });

View File

@@ -8,7 +8,7 @@ import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import { StringUtility } from "../../Shared/StringUtility";
import { config } from "../../Config";
export class SettingsPane extends ContextualPaneBase implements ViewModels.SettingsPane {
export class SettingsPane extends ContextualPaneBase {
public pageOption: ko.Observable<string>;
public customItemPerPage: ko.Observable<number>;
public crossPartitionQueryEnabled: ko.Observable<boolean>;

View File

@@ -9,7 +9,7 @@ import * as Utilities from "../../Tables/Utilities";
import EntityPropertyViewModel from "./EntityPropertyViewModel";
import TableEntityPane from "./TableEntityPane";
export default class AddTableEntityPane extends TableEntityPane implements ViewModels.AddTableEntityPane {
export default class AddTableEntityPane extends TableEntityPane {
private static _excludedFields: string[] = [TableConstants.EntityKeyNames.Timestamp];
private static _readonlyFields: string[] = [

View File

@@ -8,9 +8,10 @@ import * as Utilities from "../../Tables/Utilities";
import * as TableConstants from "../../Tables/Constants";
import EntityPropertyViewModel from "./EntityPropertyViewModel";
import * as TableEntityProcessor from "../../Tables/TableEntityProcessor";
import Explorer from "../../Explorer";
export default class EditTableEntityPane extends TableEntityPane implements ViewModels.EditTableEntityPane {
container: ViewModels.Explorer;
export default class EditTableEntityPane extends TableEntityPane {
container: Explorer;
visible: ko.Observable<boolean>;
public originEntity: Entities.ITableEntity;

View File

@@ -11,7 +11,7 @@ export interface ISelectColumn {
editable: ko.Observable<boolean>;
}
export class QuerySelectPane extends ContextualPaneBase implements ViewModels.QuerySelectPane {
export class QuerySelectPane extends ContextualPaneBase {
public titleLabel: string = "Select Columns";
public instructionLabel: string = "Select the columns that you want to query.";
public availableColumnsTableQueryLabel: string = "Available Columns";

View File

@@ -28,7 +28,7 @@ export interface IColumnSetting {
order?: number[];
}
export class TableColumnOptionsPane extends ContextualPaneBase implements ViewModels.TableColumnOptionsPane {
export class TableColumnOptionsPane extends ContextualPaneBase {
public titleLabel: string = "Column Options";
public instructionLabel: string = "Choose the columns and the order in which you want to display them in the table.";
public availableColumnsLabel: string = "Available Columns";

View File

@@ -5,7 +5,7 @@ import { ContextualPaneBase } from "./ContextualPaneBase";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
export class UploadFilePane extends ContextualPaneBase implements ViewModels.UploadFilePane {
export class UploadFilePane extends ContextualPaneBase {
public selectedFilesTitle: ko.Observable<string>;
public files: ko.Observable<FileList>;
private openOptions: ViewModels.UploadFilePaneOpenOptions;

View File

@@ -10,6 +10,7 @@ import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import { UploadDetailsRecord, UploadDetails } from "../../workers/upload/definitions";
import InfoBubbleIcon from "../../../images/info-bubble.svg";
import Explorer from "../Explorer";
const UPLOAD_FILE_SIZE_LIMIT = 2097152;
@@ -23,7 +24,7 @@ export class UploadItemsPaneAdapter implements ReactAdapter {
private selectedFilesTitle: string;
private uploadFileData: UploadDetailsRecord[];
public constructor(private container: ViewModels.Explorer) {
public constructor(private container: Explorer) {
this.parameters = ko.observable(Date.now());
this.reset();
this.triggerRender();

View File

@@ -0,0 +1,102 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PublishNotebookPaneComponent renders 1`] = `
<div
className="publishNotebookPanelContent"
>
<Stack
className="panelMainContent"
tokens={
Object {
"childrenGap": 20,
}
}
>
<StackItem>
<Text>
This notebook has your data. Please make sure you delete any sensitive data/output before publishing.
</Text>
</StackItem>
<StackItem>
<Text>
Would you like to publish and share "SampleNotebook" to the gallery?
</Text>
</StackItem>
<StackItem>
<StyledTextFieldBase
ariaLabel="Description"
label="Description"
multiline={true}
onChange={[Function]}
required={true}
rows={3}
/>
</StackItem>
<StackItem>
<StyledTextFieldBase
ariaLabel="Tags"
label="Tags"
onChange={[Function]}
placeholder="Optional tag 1, Optional tag 2"
/>
</StackItem>
<StackItem>
<StyledWithResponsiveMode
ariaLabel="Cover image"
defaultSelectedKey="URL"
label="Cover image"
onChange={[Function]}
options={
Array [
Object {
"key": "URL",
"text": "URL",
},
Object {
"key": "Custom Image",
"text": "Custom Image",
},
]
}
/>
</StackItem>
<StackItem>
<StyledTextFieldBase
ariaLabel="Cover image url"
label="Cover image url"
onChange={[Function]}
/>
</StackItem>
<StackItem>
<Text>
Preview
</Text>
</StackItem>
<StackItem>
<GalleryCardComponent
data={
Object {
"author": "CosmosDB",
"created": "2020-07-17T00:00:00Z",
"description": "",
"downloads": 0,
"favorites": 0,
"gitSha": undefined,
"id": undefined,
"isSample": false,
"name": "SampleNotebook.ipynb",
"tags": Array [
"",
],
"thumbnailUrl": undefined,
"views": 0,
}
}
isFavorite={false}
showDelete={true}
showDownload={true}
/>
</StackItem>
</Stack>
</div>
`;

View File

@@ -18,6 +18,7 @@ import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
import AddDatabaseIcon from "../../../images/AddDatabase.svg";
import SampleIcon from "../../../images/Hero-sample.svg";
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
import Explorer from "../Explorer";
/**
* TODO Remove this when fully ported to ReactJS
@@ -29,7 +30,7 @@ export class SplashScreenComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(private container: ViewModels.Explorer) {
constructor(private container: Explorer) {
this.parameters = ko.observable<number>(Date.now());
this.container.tabsManager.openedTabs.subscribe((tabs: ViewModels.Tab[]) => {
if (tabs.length === 0) {

View File

@@ -237,7 +237,7 @@ function updateTableScrollableRegionHeight(): void {
.offset().top;
var dataTablesInfoElem = $(tabElement).find(".dataTables_info");
var dataTablesPaginateElem = $(tabElement).find(".dataTables_paginate");
const explorer = window.dataExplorer as ViewModels.Explorer;
const explorer = window.dataExplorer;
const notificationConsoleHeight = explorer.isNotificationConsoleExpanded()
? 252 /** 32px(header) + 220px(content height) **/
: 32 /** Header height **/;

View File

@@ -6,6 +6,7 @@ import TableEntityListViewModel from "./TableEntityListViewModel";
import * as Entities from "../Entities";
import * as ViewModels from "../../../Contracts/ViewModels";
import * as TableColumnOptionsPane from "../../Panes/Tables/TableColumnOptionsPane";
import Explorer from "../../Explorer";
export default class TableCommands {
// Command Ids
@@ -15,9 +16,9 @@ export default class TableCommands {
public static resetColumnsCommand: string = "reset";
public static customizeColumnsCommand: string = "customizeColumns";
private _container: ViewModels.Explorer;
private _container: Explorer;
constructor(container: ViewModels.Explorer) {
constructor(container: Explorer) {
this._container = container;
}

View File

@@ -16,6 +16,7 @@ import * as ViewModels from "../../Contracts/ViewModels";
import { MessageTypes } from "../../Contracts/ExplorerContracts";
import { MessageHandler } from "../../Common/MessageHandler";
import DocumentClientUtilityBase from "../../Common/DocumentClientUtilityBase";
import Explorer from "../Explorer";
export interface CassandraTableKeys {
partitionKeys: CassandraTableKey[];
@@ -405,7 +406,7 @@ export class CassandraAPIDataClient extends TableDataClient {
public createKeyspace(
cassandraEndpoint: string,
resourceId: string,
explorer: ViewModels.Explorer,
explorer: Explorer,
createKeyspaceQuery: string
): Q.Promise<any> {
if (!createKeyspaceQuery) {
@@ -446,7 +447,7 @@ export class CassandraAPIDataClient extends TableDataClient {
public createTableAndKeyspace(
cassandraEndpoint: string,
resourceId: string,
explorer: ViewModels.Explorer,
explorer: Explorer,
createTableQuery: string,
createKeyspaceQuery?: string
): Q.Promise<any> {
@@ -506,7 +507,7 @@ export class CassandraAPIDataClient extends TableDataClient {
cassandraEndpoint: string,
resourceId: string,
deleteQuery: string,
explorer: ViewModels.Explorer
explorer: Explorer
): Q.Promise<any> {
const deferred = Q.defer<any>();
const notificationId = NotificationConsoleUtils.logConsoleMessage(
@@ -660,7 +661,7 @@ export class CassandraAPIDataClient extends TableDataClient {
cassandraEndpoint: string,
resourceId: string,
query: string,
explorer: ViewModels.Explorer
explorer: Explorer
): Q.Promise<any> {
const deferred = Q.defer();
const authType = window.authType;

View File

@@ -20,8 +20,9 @@ import DiscardIcon from "../../../images/discard.svg";
import DeleteIcon from "../../../images/delete.svg";
import { QueryIterator, ItemDefinition, Resource, ConflictDefinition } from "@azure/cosmos";
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
import Explorer from "../Explorer";
export default class ConflictsTab extends TabsBase implements ViewModels.ConflictsTab {
export default class ConflictsTab extends TabsBase {
public selectedConflictId: ko.Observable<ViewModels.ConflictId>;
public selectedConflictContent: ViewModels.Editable<string>;
public selectedConflictCurrent: ViewModels.Editable<string>;
@@ -49,7 +50,7 @@ export default class ConflictsTab extends TabsBase implements ViewModels.Conflic
public conflictIds: ko.ObservableArray<ViewModels.ConflictId>;
private _documentsIterator: MinimalQueryIterator;
private _container: ViewModels.Explorer;
private _container: Explorer;
private _acceptButtonLabel: ko.Observable<string> = ko.observable("Save");
protected _selfLink: string;

View File

@@ -17,6 +17,7 @@ import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { CosmosClient } from "../../Common/CosmosClient";
import { PlatformType } from "../../PlatformType";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import Explorer from "../Explorer";
const updateThroughputBeyondLimitWarningMessage: string = `
You are about to request an increase in throughput beyond the pre-allocated capacity.
@@ -47,8 +48,7 @@ const throughputApplyLongDelayMessage = (isAutoscale: boolean, throughput: numbe
This operation will take 1-3 business days to complete. View the latest status in Notifications.<br />
Database: ${databaseName}, ${currentThroughput(isAutoscale, throughput)}`;
export default class DatabaseSettingsTab extends TabsBase
implements ViewModels.DatabaseSettingsTab, ViewModels.WaitsForTemplate {
export default class DatabaseSettingsTab extends TabsBase implements ViewModels.WaitsForTemplate {
// editables
public isAutoPilotSelected: ViewModels.Editable<boolean>;
public throughput: ViewModels.Editable<number>;
@@ -94,7 +94,7 @@ export default class DatabaseSettingsTab extends TabsBase
private _hasProvisioningTypeChanged: ko.Computed<boolean>;
private _wasAutopilotOriginallySet: ko.Observable<boolean>;
private _offerReplacePending: ko.Computed<boolean>;
private container: ViewModels.Explorer;
private container: Explorer;
constructor(options: ViewModels.TabOptions) {
super(options);

View File

@@ -25,6 +25,7 @@ import SynapseIcon from "../../../images/synapse-link.svg";
import { extractPartitionKey, PartitionKeyDefinition, QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import Explorer from "../Explorer";
export default class DocumentsTab extends TabsBase implements ViewModels.DocumentsTab {
public selectedDocumentId: ko.Observable<ViewModels.DocumentId>;
@@ -957,7 +958,7 @@ export default class DocumentsTab extends TabsBase implements ViewModels.Documen
);
}
public static _createUploadButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
public static _createUploadButton(container: Explorer): ViewModels.NavbarButtonConfig {
const label = "Upload Item";
return {
iconSrc: UploadIcon,

View File

@@ -3,12 +3,13 @@ import { GalleryAndNotebookViewerComponentProps } from "../Controls/NotebookGall
import { GalleryAndNotebookViewerComponentAdapter } from "../Controls/NotebookGallery/GalleryAndNotebookViewerComponentAdapter";
import { GalleryTab as GalleryViewerTab, SortBy } from "../Controls/NotebookGallery/GalleryViewerComponent";
import TabsBase from "./TabsBase";
import Explorer from "../Explorer";
/**
* Notebook gallery tab
*/
export default class GalleryTab extends TabsBase implements ViewModels.Tab {
private container: ViewModels.Explorer;
private container: Explorer;
public galleryAndNotebookViewerComponentAdapter: GalleryAndNotebookViewerComponentAdapter;
constructor(options: ViewModels.GalleryTabOptions) {
@@ -29,7 +30,7 @@ export default class GalleryTab extends TabsBase implements ViewModels.Tab {
this.galleryAndNotebookViewerComponentAdapter = new GalleryAndNotebookViewerComponentAdapter(props);
}
protected getContainer(): ViewModels.Explorer {
protected getContainer(): Explorer {
return this.container;
}
}

View File

@@ -7,6 +7,8 @@ import { GraphExplorerAdapter } from "../Graph/GraphExplorerComponent/GraphExplo
import { GraphAccessor, GraphExplorerError } from "../Graph/GraphExplorerComponent/GraphExplorer";
import NewVertexIcon from "../../../images/NewVertex.svg";
import StyleIcon from "../../../images/Style.svg";
import GraphStylingPane from "../Panes/GraphStylingPane";
import NewVertexPane from "../Panes/NewVertexPane";
export interface GraphIconMap {
[key: string]: { data: string; format: string };
@@ -41,8 +43,8 @@ export default class GraphTab extends TabsBase implements ViewModels.Tab {
private graphConfigUiData: ViewModels.GraphConfigUiData;
private isFilterQueryLoading: ko.Observable<boolean>;
private isValidQuery: ko.Observable<boolean>;
private newVertexPane: ViewModels.NewVertexPane;
private graphStylingPane: ViewModels.GraphStylingPane;
private newVertexPane: NewVertexPane;
private graphStylingPane: GraphStylingPane;
private collectionPartitionKeyProperty: string;
constructor(options: ViewModels.GraphTabOptions) {

View File

@@ -13,10 +13,11 @@ import { CosmosClient } from "../../Common/CosmosClient";
import { HashMap } from "../../Common/HashMap";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import { PlatformType } from "../../PlatformType";
import Explorer from "../Explorer";
export default class MongoShellTab extends TabsBase implements ViewModels.MongoShellTab {
public url: ko.Computed<string>;
private _container: ViewModels.Explorer;
private _container: Explorer;
private _runtimeEndpoint: string;
private _logTraces: HashMap<number>;

View File

@@ -27,10 +27,11 @@ import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/Notebook
import { NotebookConfigurationUtils } from "../../Utils/NotebookConfigurationUtils";
import { KernelSpecsDisplay, NotebookClientV2 } from "../Notebook/NotebookClientV2";
import { config } from "../../Config";
import Explorer from "../Explorer";
export default class NotebookTabV2 extends TabsBase implements ViewModels.Tab {
private static clientManager: NotebookClientV2;
private container: ViewModels.Explorer;
private container: Explorer;
public notebookPath: ko.Observable<string>;
private selectedSparkPool: ko.Observable<string>;
private notebookComponentAdapter: NotebookComponentAdapter;
@@ -104,7 +105,7 @@ export default class NotebookTabV2 extends TabsBase implements ViewModels.Tab {
return await this.configureServiceEndpoints(this.notebookComponentAdapter.getCurrentKernelName());
}
protected getContainer(): ViewModels.Explorer {
protected getContainer(): Explorer {
return this.container;
}

View File

@@ -7,6 +7,7 @@ import {
NotebookViewerComponentProps
} from "../Controls/NotebookViewer/NotebookViewerComponent";
import TabsBase from "./TabsBase";
import Explorer from "../Explorer";
/**
* Notebook Viewer tab
@@ -29,7 +30,7 @@ class NotebookViewerComponentAdapter implements ReactAdapter {
}
export default class NotebookViewerTab extends TabsBase implements ViewModels.Tab {
private container: ViewModels.Explorer;
private container: Explorer;
public notebookUrl: string;
public notebookViewerComponentAdapter: NotebookViewerComponentAdapter;
@@ -49,7 +50,7 @@ export default class NotebookViewerTab extends TabsBase implements ViewModels.Ta
});
}
protected getContainer(): ViewModels.Explorer {
protected getContainer(): Explorer {
return this.container;
}

View File

@@ -2,21 +2,22 @@ import * as ko from "knockout";
import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import Explorer from "../Explorer";
import { CollectionStub, DatabaseStub } from "../../Explorer/OpenActionsStubs";
import QueryTab from "./QueryTab";
import { View } from "@nteract/data-explorer/lib/utilities/types";
import { PartitionKey } from "../../Contracts/DataModels";
describe("Query Tab", () => {
function getNewQueryTabForContainer(container: ViewModels.Explorer): ViewModels.QueryTab {
const database: ViewModels.Database = new DatabaseStub({
function getNewQueryTabForContainer(container: Explorer): ViewModels.QueryTab {
const database = {
container: container,
id: ko.observable<string>("test"),
isDatabaseShared: () => false
});
const collection: ViewModels.Collection = new CollectionStub({
} as ViewModels.Database;
const collection = {
container: container,
databaseId: "test",
id: ko.observable<string>("test")
});
} as ViewModels.Collection;
return new QueryTab({
tabKind: ViewModels.CollectionTabKind.Query,
@@ -33,19 +34,12 @@ describe("Query Tab", () => {
}
describe("shouldSetSystemPartitionKeyContainerPartitionKeyValueUndefined", () => {
const collection: ViewModels.Collection = new CollectionStub({
id: "withoutsystempk",
const collection = {
id: ko.observable<string>("withoutsystempk"),
partitionKey: {
systemKey: true
}
});
const collectionSystemPK: ViewModels.Collection = new CollectionStub({
id: "withsystempk",
partitionKey: {
systemKey: true
}
});
} as ViewModels.Collection;
it("no container with system pk, should not set partition key option", () => {
const iteratorOptions = QueryTab.getIteratorOptions(collection);
@@ -54,7 +48,7 @@ describe("Query Tab", () => {
});
describe("isQueryMetricsEnabled()", () => {
let explorer: ViewModels.Explorer;
let explorer: Explorer;
beforeEach(() => {
explorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false });
@@ -74,7 +68,7 @@ describe("Query Tab", () => {
});
describe("Save Queries command button", () => {
let explorer: ViewModels.Explorer;
let explorer: Explorer;
beforeEach(() => {
explorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false });

View File

@@ -115,7 +115,7 @@ export default class QueryTab extends TabsBase implements ViewModels.QueryTab, V
};
this._isSaveQueriesEnabled = ko.computed<boolean>(() => {
const container: ViewModels.Explorer = this.collection && this.collection.container;
const container = this.collection && this.collection.container;
return (container && (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph())) || false;
});

View File

@@ -13,6 +13,7 @@ import ExecuteQueryIcon from "../../../images/ExecuteQuery.svg";
import AddEntityIcon from "../../../images/AddEntity.svg";
import EditEntityIcon from "../../../images/Edit-entity.svg";
import DeleteEntitiesIcon from "../../../images/DeleteEntities.svg";
import Explorer from "../Explorer";
// Will act as table explorer class
export default class QueryTablesTab extends TabsBase {
@@ -31,7 +32,7 @@ export default class QueryTablesTab extends TabsBase {
public deleteEntityButton: ViewModels.Button;
public queryBuilderButton: ViewModels.Button;
public queryTextButton: ViewModels.Button;
public container: ViewModels.Explorer;
public container: Explorer;
constructor(options: ViewModels.TabOptions) {
super(options);

View File

@@ -177,7 +177,7 @@ describe("Settings tab", () => {
});
describe("Should update collection", () => {
let explorer: ViewModels.Explorer;
let explorer: Explorer;
beforeEach(() => {
explorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false });
@@ -259,7 +259,7 @@ describe("Settings tab", () => {
});
describe("Get Conflict Resolution configuration from user", () => {
let explorer: ViewModels.Explorer;
let explorer: Explorer;
beforeEach(() => {
explorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false });

View File

@@ -17,6 +17,7 @@ import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { CosmosClient } from "../../Common/CosmosClient";
import { PlatformType } from "../../PlatformType";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import Explorer from "../Explorer";
const ttlWarning: string = `
The system will automatically delete items based on the TTL value (in seconds) you provide, without needing a delete operation explicitly issued by a client application.
@@ -124,7 +125,7 @@ enum ChangeFeedPolicyToggledState {
On = "On"
}
export default class SettingsTab extends TabsBase implements ViewModels.SettingsTab, ViewModels.WaitsForTemplate {
export default class SettingsTab extends TabsBase implements ViewModels.WaitsForTemplate {
public GEOGRAPHY: string = "Geography";
public GEOMETRY: string = "Geometry";
@@ -218,7 +219,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.Settings
public throughputModeRadioName: string;
private _offerReplacePending: ko.PureComputed<boolean>;
private container: ViewModels.Explorer;
private container: Explorer;
private _wasAutopilotOriginallySet: ko.Observable<boolean>;
private _isAutoPilotDirty: ko.Computed<boolean>;
private _hasProvisioningTypeChanged: ko.Computed<boolean>;

View File

@@ -2,12 +2,13 @@ import * as ko from "knockout";
import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels";
import TabsBase from "./TabsBase";
import Explorer from "../Explorer";
export default class SparkMasterTab extends TabsBase {
public sparkMasterSrc: ko.Observable<string>;
private _clusterConnectionInfo: DataModels.SparkClusterConnectionInfo;
private _container: ViewModels.Explorer;
private _container: Explorer;
constructor(options: ViewModels.SparkMasterTabOptions) {
super(options);

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