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/ **/node_modules/
dist/ dist/
Contracts/
src/Api/Apis.ts src/Api/Apis.ts
src/AuthType.ts src/AuthType.ts
src/Bindings/BindingHandlersRegisterer.ts src/Bindings/BindingHandlersRegisterer.ts
@@ -137,7 +138,6 @@ src/Explorer/Panes/AddDatabasePane.test.ts
src/Explorer/Panes/AddDatabasePane.ts src/Explorer/Panes/AddDatabasePane.ts
src/Explorer/Panes/BrowseQueriesPane.ts src/Explorer/Panes/BrowseQueriesPane.ts
src/Explorer/Panes/CassandraAddCollectionPane.ts src/Explorer/Panes/CassandraAddCollectionPane.ts
src/Explorer/Panes/ClusterLibraryPane.ts
src/Explorer/Panes/ContextualPaneBase.ts src/Explorer/Panes/ContextualPaneBase.ts
src/Explorer/Panes/DeleteCollectionConfirmationPane.test.ts src/Explorer/Panes/DeleteCollectionConfirmationPane.test.ts
src/Explorer/Panes/DeleteCollectionConfirmationPane.ts src/Explorer/Panes/DeleteCollectionConfirmationPane.ts
@@ -145,7 +145,6 @@ src/Explorer/Panes/DeleteDatabaseConfirmationPane.test.ts
src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts
src/Explorer/Panes/ExecuteSprocParamsPane.ts src/Explorer/Panes/ExecuteSprocParamsPane.ts
src/Explorer/Panes/GraphStylingPane.ts src/Explorer/Panes/GraphStylingPane.ts
src/Explorer/Panes/LibraryManagePane.ts
src/Explorer/Panes/LoadQueryPane.ts src/Explorer/Panes/LoadQueryPane.ts
src/Explorer/Panes/NewVertexPane.ts src/Explorer/Panes/NewVertexPane.ts
src/Explorer/Panes/PaneComponents.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/Directory/DirectoryListComponent.tsx
src/Explorer/Controls/Editor/EditorReact.tsx src/Explorer/Controls/Editor/EditorReact.tsx
src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.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.test.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.tsx src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.tsx

View File

@@ -39,6 +39,7 @@ module.exports = {
curly: "error", curly: "error",
"@typescript-eslint/no-unused-vars": "error", "@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-extraneous-class": "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 # 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 ## Getting Started
- `npm install` - `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 1. Copy .env.example to .env and fill in all variables
2. Run `npm run test:e2e` 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 # Contributing
Please read the [contribution guidelines](./CONTRIBUTING.md). Please read the [contribution guidelines](./CONTRIBUTING.md).

View File

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

167
package-lock.json generated
View File

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

View File

@@ -34,6 +34,8 @@
"@nteract/transform-vega": "7.0.6", "@nteract/transform-vega": "7.0.6",
"@octokit/rest": "17.9.2", "@octokit/rest": "17.9.2",
"@phosphor/widgets": "1.9.3", "@phosphor/widgets": "1.9.3",
"@types/mkdirp": "1.0.1",
"@types/node-fetch": "2.5.7",
"@uifabric/react-cards": "0.109.110", "@uifabric/react-cards": "0.109.110",
"@uifabric/styling": "7.13.7", "@uifabric/styling": "7.13.7",
"abort-controller": "3.0.0", "abort-controller": "3.0.0",
@@ -60,6 +62,7 @@
"jquery-typeahead": "2.10.6", "jquery-typeahead": "2.10.6",
"jquery-ui-dist": "1.12.1", "jquery-ui-dist": "1.12.1",
"knockout": "3.5.1", "knockout": "3.5.1",
"mkdirp": "1.0.4",
"monaco-editor": "0.15.6", "monaco-editor": "0.15.6",
"object.entries": "1.1.0", "object.entries": "1.1.0",
"office-ui-fabric-react": "7.121.10", "office-ui-fabric-react": "7.121.10",
@@ -147,6 +150,7 @@
"less-vars-loader": "1.1.0", "less-vars-loader": "1.1.0",
"mini-css-extract-plugin": "0.4.3", "mini-css-extract-plugin": "0.4.3",
"monaco-editor-webpack-plugin": "1.7.0", "monaco-editor-webpack-plugin": "1.7.0",
"node-fetch": "2.6.0",
"prettier": "1.19.1", "prettier": "1.19.1",
"puppeteer": "4.0.0", "puppeteer": "4.0.0",
"raw-loader": "0.5.1", "raw-loader": "0.5.1",
@@ -187,7 +191,8 @@
"build:contracts": "npm run compile:contracts", "build:contracts": "npm run compile:contracts",
"strictEligibleFiles": "node ./strict-migration-tools/index.js", "strictEligibleFiles": "node ./strict-migration-tools/index.js",
"autoAddStrictEligibleFiles": "node ./strict-migration-tools/autoAdd.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": { "repository": {
"type": "git", "type": "git",

View File

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

View File

@@ -24,6 +24,7 @@ import { MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
import { OfferUtils } from "../Utils/OfferUtils"; import { OfferUtils } from "../Utils/OfferUtils";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
export function getCommonQueryOptions(options: FeedOptions): any { export function getCommonQueryOptions(options: FeedOptions): any {
const storedItemPerPageSetting: number = LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage); const storedItemPerPageSetting: number = LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage);
@@ -142,7 +143,7 @@ export abstract class DataAccessUtilityBase {
public executeStoredProcedure( public executeStoredProcedure(
collection: ViewModels.Collection, collection: ViewModels.Collection,
storedProcedure: ViewModels.StoredProcedure, storedProcedure: StoredProcedure,
partitionKeyValue: any, partitionKeyValue: any,
params: any[] params: any[]
): Q.Promise<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( public queryConflicts(
databaseId: string, databaseId: string,
containerId: string, containerId: string,

View File

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

View File

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

View File

@@ -16,6 +16,7 @@ import { MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient"; import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
import { MinimalQueryIterator } from "./IteratorUtilities";
const defaultHeaders = { const defaultHeaders = {
[HttpHeaders.apiType]: ApiType.MongoDB.toString(), [HttpHeaders.apiType]: ApiType.MongoDB.toString(),
@@ -23,7 +24,7 @@ const defaultHeaders = {
[CosmosSDKConstants.HttpHeaders.Version]: "2017-11-15" [CosmosSDKConstants.HttpHeaders.Version]: "2017-11-15"
}; };
function authHeaders(): any { function authHeaders() {
if (window.authType === AuthType.EncryptedToken) { if (window.authType === AuthType.EncryptedToken) {
return { [HttpHeaders.guestAccessToken]: CosmosClient.accessToken() }; return { [HttpHeaders.guestAccessToken]: CosmosClient.accessToken() };
} else { } 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; let continuationToken: string;
return { return {
fetchNext: () => { fetchNext: () => {
return queryDocuments(databaseId, collection, false, query).then(response => { return queryDocuments(databaseId, collection, false, query).then(response => {
continuationToken = response.continuationToken; continuationToken = response.continuationToken;
const headers = {} as any; const headers: { [key: string]: string | number } = {};
response.headers.forEach((value: any, key: any) => { response.headers.forEach((value, key) => {
headers[key] = value; headers[key] = value;
}); });
return { return {
resources: response.documents, resources: response.documents,
headers, headers,
requestCharge: headers[CosmosSDKConstants.HttpHeaders.RequestCharge], requestCharge: Number(headers[CosmosSDKConstants.HttpHeaders.RequestCharge]),
activityId: headers[CosmosSDKConstants.HttpHeaders.ActivityId], activityId: String(headers[CosmosSDKConstants.HttpHeaders.ActivityId]),
hasMoreResults: !!continuationToken hasMoreResults: !!continuationToken
}; };
}); });
@@ -114,7 +115,8 @@ export function queryDocuments(
headers: response.headers 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, databaseId: string,
collection: Collection, collection: Collection,
partitionKeyProperty: string, partitionKeyProperty: string,
documentContent: any documentContent: unknown
): Promise<DataModels.DocumentId> { ): Promise<DataModels.DocumentId> {
const databaseAccount = CosmosClient.databaseAccount(); const databaseAccount = CosmosClient.databaseAccount();
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
@@ -204,7 +206,7 @@ export function updateDocument(
databaseId: string, databaseId: string,
collection: Collection, collection: Collection,
documentId: ViewModels.DocumentId, documentId: ViewModels.DocumentId,
documentContent: any documentContent: unknown
): Promise<DataModels.DocumentId> { ): Promise<DataModels.DocumentId> {
const databaseAccount = CosmosClient.databaseAccount(); const databaseAccount = CosmosClient.databaseAccount();
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
@@ -228,7 +230,7 @@ export function updateDocument(
return window return window
.fetch(`${endpoint}?${queryString.stringify(params)}`, { .fetch(`${endpoint}?${queryString.stringify(params)}`, {
method: "PUT", method: "PUT",
body: documentContent, body: JSON.stringify(documentContent),
headers: { headers: {
...defaultHeaders, ...defaultHeaders,
...authHeaders(), ...authHeaders(),
@@ -248,7 +250,7 @@ export function deleteDocument(
databaseId: string, databaseId: string,
collection: Collection, collection: Collection,
documentId: ViewModels.DocumentId documentId: ViewModels.DocumentId
): Promise<any> { ): Promise<void> {
const databaseAccount = CosmosClient.databaseAccount(); const databaseAccount = CosmosClient.databaseAccount();
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint; const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/"); const idComponents = documentId.self.split("/");
@@ -295,7 +297,7 @@ export function createMongoCollectionWithProxy(
sharedThroughput: boolean, sharedThroughput: boolean,
isSharded: boolean, isSharded: boolean,
autopilotOptions?: DataModels.RpOptions autopilotOptions?: DataModels.RpOptions
): Promise<any> { ): Promise<DataModels.Collection> {
const databaseAccount = CosmosClient.databaseAccount(); const databaseAccount = CosmosClient.databaseAccount();
const params: DataModels.MongoParameters = { const params: DataModels.MongoParameters = {
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint, resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
@@ -335,7 +337,7 @@ export function createMongoCollectionWithProxy(
) )
.then(response => { .then(response => {
if (response.ok) { if (response.ok) {
return undefined; return response.json();
} }
return errorHandling(response, "creating collection", params); return errorHandling(response, "creating collection", params);
}); });
@@ -352,7 +354,7 @@ export function createMongoCollectionWithARM(
sharedThroughput: boolean, sharedThroughput: boolean,
isSharded: boolean, isSharded: boolean,
additionalOptions?: DataModels.RpOptions additionalOptions?: DataModels.RpOptions
): Promise<any> { ): Promise<DataModels.CreateCollectionWithRpResponse> {
const databaseAccount = CosmosClient.databaseAccount(); const databaseAccount = CosmosClient.databaseAccount();
const params: DataModels.MongoParameters = { const params: DataModels.MongoParameters = {
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint, resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
@@ -396,7 +398,9 @@ export function getEndpoint(databaseAccount: ViewModels.DatabaseAccount): string
return url; 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(); const errorMessage = await response.text();
// Log the error where the user can see it // Log the error where the user can see it
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
@@ -420,7 +424,7 @@ export async function _createMongoCollectionWithARM(
armEndpoint: string, armEndpoint: string,
params: DataModels.MongoParameters, params: DataModels.MongoParameters,
rpOptions: DataModels.RpOptions rpOptions: DataModels.RpOptions
): Promise<any> { ): Promise<DataModels.CreateCollectionWithRpResponse> {
const rpPayloadToCreateCollection: DataModels.MongoCreationRequest = { const rpPayloadToCreateCollection: DataModels.MongoCreationRequest = {
properties: { properties: {
resource: { resource: {
@@ -448,12 +452,13 @@ export async function _createMongoCollectionWithARM(
} }
try { try {
await new ResourceProviderClient(armEndpoint).putAsync( return new ResourceProviderClient<DataModels.CreateCollectionWithRpResponse>(armEndpoint).putAsync(
getARMCreateCollectionEndpoint(params), getARMCreateCollectionEndpoint(params),
DataExplorerConstants.ArmApiVersions.publicVersion, DataExplorerConstants.ArmApiVersions.publicVersion,
rpPayloadToCreateCollection rpPayloadToCreateCollection
); );
} catch (response) { } 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 * as Logger from "./Logger";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import { QueryUtils } from "../Utils/QueryUtils"; import { QueryUtils } from "../Utils/QueryUtils";
import Explorer from "../Explorer/Explorer";
export class QueriesClient implements ViewModels.QueriesClient { export class QueriesClient implements ViewModels.QueriesClient {
private static readonly PartitionKey: DataModels.PartitionKey = { 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 FetchQuery: string = "SELECT * FROM c";
private static readonly FetchMongoQuery: string = "{}"; private static readonly FetchMongoQuery: string = "{}";
public constructor(private container: ViewModels.Explorer) {} public constructor(private container: Explorer) {}
public async setupQueriesCollection(): Promise<DataModels.Collection> { public async setupQueriesCollection(): Promise<DataModels.Collection> {
const queriesCollection: ViewModels.Collection = this.findQueriesCollection(); const queriesCollection: ViewModels.Collection = this.findQueriesCollection();

View File

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

View File

@@ -1,32 +1,21 @@
import * as DataModels from "./DataModels"; import * as DataModels from "./DataModels";
import * as Entities from "../Explorer/Tables/Entities";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import DocumentClientUtilityBase from "../Common/DocumentClientUtilityBase"; import DocumentClientUtilityBase from "../Common/DocumentClientUtilityBase";
import Q from "q"; 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 { AccessibleVerticalList } from "../Explorer/Tree/AccessibleVerticalList";
import { ArcadiaWorkspaceItem } from "../Explorer/Controls/Arcadia/ArcadiaMenuPicker"; import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
import { CassandraTableKey, CassandraTableKeys, TableDataClient } from "../Explorer/Tables/TableDataClient";
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { ExecuteSprocParam } from "../Explorer/Panes/ExecuteSprocParamsPane";
import { GitHubClient } from "../GitHub/GitHubClient"; import { GitHubClient } from "../GitHub/GitHubClient";
import { IColumnSetting } from "../Explorer/Panes/Tables/TableColumnOptionsPane";
import { JunoClient, IGalleryItem } from "../Juno/JunoClient"; import { JunoClient, IGalleryItem } from "../Juno/JunoClient";
import { Library } from "./DataModels";
import { MostRecentActivity } from "../Explorer/MostRecentActivity/MostRecentActivity";
import { NotebookContentItem } from "../Explorer/Notebook/NotebookContentItem"; import { NotebookContentItem } from "../Explorer/Notebook/NotebookContentItem";
import { PlatformType } from "../PlatformType";
import { QueryMetrics } from "@azure/cosmos"; 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 { UploadDetails } from "../workers/upload/definitions";
import { UploadItemsPaneAdapter } from "../Explorer/Panes/UploadItemsPaneAdapter"; import Explorer from "../Explorer/Explorer";
import { ReactAdapter } from "../Bindings/ReactBindingHandler"; 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 { export interface ExplorerOptions {
documentClientUtility: DocumentClientUtilityBase; documentClientUtility: DocumentClientUtilityBase;
@@ -42,266 +31,12 @@ export interface NavbarButtonConfig extends CommandButtonComponentProps {}
export interface DatabaseAccount extends DataModels.DatabaseAccount {} 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 { export interface KernelConnectionMetadata {
name: string; name: string;
configurationEndpoints: DataModels.NotebookConfigurationEndpoints; configurationEndpoints: DataModels.NotebookConfigurationEndpoints;
notebookConnectionInfo: DataModels.NotebookWorkspaceConnectionInfo; 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 { export interface TokenProvider {
getAuthHeader(): Promise<Headers>; getAuthHeader(): Promise<Headers>;
} }
@@ -505,48 +240,6 @@ export interface ConflictId {
loadConflict(): Q.Promise<any>; 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 * Options used to initialize pane
*/ */
@@ -598,78 +291,6 @@ export interface AddCollectionPaneOptions extends PaneOptions {
databaseSelfLink?: string; 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 { export interface UploadFilePaneOpenOptions {
paneTitle: string; paneTitle: string;
selectFileInputLabel: string; selectFileInputLabel: string;
@@ -692,10 +313,6 @@ export interface StringInputPaneOpenOptions {
defaultInput?: string; defaultInput?: string;
} }
export interface UploadFilePane extends ContextualPane {
openWithOptions: (options: UploadFilePaneOpenOptions) => void;
}
/** /**
* Graph configuration * Graph configuration
*/ */
@@ -740,41 +357,6 @@ export interface InputProperty {
values: InputPropertyValue[]; 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> { export interface Editable<T> extends ko.Observable<T> {
setBaseline(baseline: T): void; setBaseline(baseline: T): void;
@@ -981,111 +563,6 @@ export interface DocumentsTab extends Tab {
loadNextPage(): Q.Promise<any>; 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 { export interface WaitsForTemplate {
isTemplateReady: ko.Observable<boolean>; isTemplateReady: ko.Observable<boolean>;
} }

View File

@@ -123,12 +123,4 @@ describe("Component Registerer", () => {
it("should register throughput-input component", () => { it("should register throughput-input component", () => {
expect(ko.components.isRegistered("throughput-input")).toBe(true); 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("upload-file-pane", new PaneComponents.UploadFilePaneComponent());
ko.components.register("string-input-pane", new PaneComponents.StringInputPaneComponent()); ko.components.register("string-input-pane", new PaneComponents.StringInputPaneComponent());
ko.components.register("setup-notebooks-pane", new PaneComponents.SetupNotebooksPaneComponent()); 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()); 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 DeleteTriggerIcon from "../../images/DeleteTrigger.svg";
import DeleteUDFIcon from "../../images/DeleteUDF.svg"; import DeleteUDFIcon from "../../images/DeleteUDF.svg";
import DeleteSprocIcon from "../../images/DeleteSproc.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 { export interface CollectionContextMenuButtonParams {
databaseId: string; databaseId: string;
@@ -26,7 +30,7 @@ export interface DatabaseContextMenuButtonParams {
*/ */
export class ResourceTreeContextMenuButtonFactory { export class ResourceTreeContextMenuButtonFactory {
public static createDatabaseContextMenu( public static createDatabaseContextMenu(
container: ViewModels.Explorer, container: Explorer,
selectedDatabase: ViewModels.Database selectedDatabase: ViewModels.Database
): TreeNodeMenuItem[] { ): TreeNodeMenuItem[] {
const newCollectionMenuItem: TreeNodeMenuItem = { const newCollectionMenuItem: TreeNodeMenuItem = {
@@ -44,7 +48,7 @@ export class ResourceTreeContextMenuButtonFactory {
} }
public static createCollectionContextMenuButton( public static createCollectionContextMenuButton(
container: ViewModels.Explorer, container: Explorer,
selectedCollection: ViewModels.Collection selectedCollection: ViewModels.Collection
): TreeNodeMenuItem[] { ): TreeNodeMenuItem[] {
const items: TreeNodeMenuItem[] = []; const items: TreeNodeMenuItem[] = [];
@@ -115,8 +119,8 @@ export class ResourceTreeContextMenuButtonFactory {
} }
public static createStoreProcedureContextMenuItems( public static createStoreProcedureContextMenuItems(
container: ViewModels.Explorer, container: Explorer,
storedProcedure: ViewModels.StoredProcedure storedProcedure: StoredProcedure
): TreeNodeMenuItem[] { ): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) { if (container.isPreferredApiCassandra()) {
return []; return [];
@@ -131,10 +135,7 @@ export class ResourceTreeContextMenuButtonFactory {
]; ];
} }
public static createTriggerContextMenuItems( public static createTriggerContextMenuItems(container: Explorer, trigger: Trigger): TreeNodeMenuItem[] {
container: ViewModels.Explorer,
trigger: ViewModels.Trigger
): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) { if (container.isPreferredApiCassandra()) {
return []; return [];
} }
@@ -149,8 +150,8 @@ export class ResourceTreeContextMenuButtonFactory {
} }
public static createUserDefinedFunctionContextMenuItems( public static createUserDefinedFunctionContextMenuItems(
container: ViewModels.Explorer, container: Explorer,
userDefinedFunction: ViewModels.UserDefinedFunction userDefinedFunction: UserDefinedFunction
): TreeNodeMenuItem[] { ): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) { if (container.isPreferredApiCassandra()) {
return []; return [];

View File

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

View File

@@ -1,6 +1,5 @@
import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "office-ui-fabric-react"; import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import * as ViewModels from "../../../Contracts/ViewModels";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import { RepoListItem } from "./GitHubReposComponent"; import { RepoListItem } from "./GitHubReposComponent";
@@ -9,9 +8,10 @@ import * as GitHubUtils from "../../../Utils/GitHubUtils";
import { IGitHubRepo } from "../../../GitHub/GitHubClient"; import { IGitHubRepo } from "../../../GitHub/GitHubClient";
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import UrlUtility from "../../../Common/UrlUtility"; import UrlUtility from "../../../Common/UrlUtility";
import Explorer from "../../Explorer";
export interface AddRepoComponentProps { export interface AddRepoComponentProps {
container: ViewModels.Explorer; container: Explorer;
getRepo: (owner: string, repo: string) => Promise<IGitHubRepo>; getRepo: (owner: string, repo: string) => Promise<IGitHubRepo>;
pinRepo: (item: RepoListItem) => void; 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 React from "react";
import * as ViewModels from "../../../Contracts/ViewModels";
import { JunoClient, IGalleryItem } from "../../../Juno/JunoClient"; import { JunoClient, IGalleryItem } from "../../../Juno/JunoClient";
import { GalleryTab, SortBy, GalleryViewerComponentProps, GalleryViewerComponent } from "./GalleryViewerComponent"; import { GalleryTab, SortBy, GalleryViewerComponentProps, GalleryViewerComponent } from "./GalleryViewerComponent";
import { NotebookViewerComponentProps, NotebookViewerComponent } from "../NotebookViewer/NotebookViewerComponent"; import { NotebookViewerComponentProps, NotebookViewerComponent } from "../NotebookViewer/NotebookViewerComponent";
import * as GalleryUtils from "../../../Utils/GalleryUtils"; import * as GalleryUtils from "../../../Utils/GalleryUtils";
import Explorer from "../../Explorer";
export interface GalleryAndNotebookViewerComponentProps { export interface GalleryAndNotebookViewerComponentProps {
container?: ViewModels.Explorer; container?: Explorer;
junoClient: JunoClient; junoClient: JunoClient;
notebookUrl?: string; notebookUrl?: string;
galleryItem?: IGalleryItem; galleryItem?: IGalleryItem;

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
/* Utilities for validation */ /* 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); let numericValue = parseInt(newValue);
if (!isNaN(numericValue) && isFinite(numericValue)) { if (!isNaN(numericValue) && isFinite(numericValue)) {
if (minValue !== undefined && numericValue < minValue) { if (minValue !== undefined && numericValue < minValue) {
@@ -16,7 +16,7 @@ export const onValidateValueChange = (newValue: string, minValue?: number, maxVa
return undefined; 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); const numericValue = parseInt(newValue);
if (!isNaN(numericValue) && isFinite(numericValue)) { if (!isNaN(numericValue) && isFinite(numericValue)) {
const newValue = numericValue + step; const newValue = numericValue + step;
@@ -25,7 +25,7 @@ export const onIncrementValue = (newValue: string, step: number, max?: number):
return undefined; 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); const numericValue = parseInt(newValue);
if (!isNaN(numericValue) && isFinite(numericValue)) { if (!isNaN(numericValue) && isFinite(numericValue)) {
const newValue = numericValue - step; 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 * as ViewModels from "../../Contracts/ViewModels";
import DocumentClientUtilityBase from "../../Common/DocumentClientUtilityBase"; import DocumentClientUtilityBase from "../../Common/DocumentClientUtilityBase";
import Q from "q"; import Q from "q";
import { CollectionStub, DatabaseStub, ExplorerStub } from "../OpenActionsStubs";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator"; import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import { CosmosClient } from "../../Common/CosmosClient"; import { CosmosClient } from "../../Common/CosmosClient";
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient"; import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
import Explorer from "../Explorer";
describe("ContainerSampleGenerator", () => { describe("ContainerSampleGenerator", () => {
const createExplorerStub = (database: ViewModels.Database): ExplorerStub => { const createExplorerStub = (database: ViewModels.Database): Explorer => {
const explorerStub = new ExplorerStub(); const explorerStub = {} as Explorer;
explorerStub.nonSystemDatabases = ko.computed(() => [database]); explorerStub.nonSystemDatabases = ko.computed(() => [database]);
explorerStub.isPreferredApiGraph = ko.computed<boolean>(() => false); explorerStub.isPreferredApiGraph = ko.computed<boolean>(() => false);
explorerStub.isPreferredApiMongoDB = 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 collection = { id: ko.observable(sampleCollectionId) } as ViewModels.Collection;
const database = new DatabaseStub({ const database = {
id: ko.observable(sampleDatabaseId), id: ko.observable(sampleDatabaseId),
collections: ko.observableArray([collection]) collections: ko.observableArray<ViewModels.Collection>([collection])
}); } as ViewModels.Database;
database.findCollectionWithId = () => collection; database.findCollectionWithId = () => collection;
const explorerStub = createExplorerStub(database); 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)" "g.addV('person').property(id, '1').property('_partitionKey','pk').property('name', 'Eva').property('age', 44)"
] ]
}; };
const collection = new CollectionStub({ id: ko.observable(sampleCollectionId) }); const collection = { id: ko.observable(sampleCollectionId) } as ViewModels.Collection;
const database = new DatabaseStub({ const database = {
id: ko.observable(sampleDatabaseId), id: ko.observable(sampleDatabaseId),
collections: ko.observableArray([collection]) collections: ko.observableArray<ViewModels.Collection>([collection])
}); } as ViewModels.Database;
database.findCollectionWithId = () => collection; database.findCollectionWithId = () => collection;
collection.databaseId = database.id(); collection.databaseId = database.id();

View File

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

View File

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

View File

@@ -2,10 +2,11 @@ import * as ViewModels from "../../Contracts/ViewModels";
import { ContainerSampleGenerator } from "./ContainerSampleGenerator"; import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import Explorer from "../Explorer";
export class DataSamplesUtil { export class DataSamplesUtil {
private static readonly DialogTitle = "Create Sample Container"; 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 * 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 NotebookV2Tab from "./Tabs/NotebookV2Tab";
import Q from "q"; import Q from "q";
import ResourceTokenCollection from "./Tree/ResourceTokenCollection"; import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
import SparkMasterTab from "./Tabs/SparkMasterTab";
import TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import TerminalTab from "./Tabs/TerminalTab"; import TerminalTab from "./Tabs/TerminalTab";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
@@ -36,7 +35,6 @@ import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane"; import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane";
import { CassandraApi } from "../Api/Apis"; import { CassandraApi } from "../Api/Apis";
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient"; import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
import { ClusterLibraryPane } from "./Panes/ClusterLibraryPane";
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter"; import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
import { config } from "../Config"; import { config } from "../Config";
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
@@ -52,7 +50,6 @@ import { FileSystemUtil } from "./Notebook/FileSystemUtil";
import { handleOpenAction } from "./OpenActions"; import { handleOpenAction } from "./OpenActions";
import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation"; import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
import { IGalleryItem } from "../Juno/JunoClient"; import { IGalleryItem } from "../Juno/JunoClient";
import { LibraryManagePane } from "./Panes/LibraryManagePane";
import { LoadQueryPane } from "./Panes/LoadQueryPane"; import { LoadQueryPane } from "./Panes/LoadQueryPane";
import * as Logger from "../Common/Logger"; import * as Logger from "../Common/Logger";
import { MessageHandler } from "../Common/MessageHandler"; import { MessageHandler } from "../Common/MessageHandler";
@@ -72,7 +69,6 @@ import { RouteHandler } from "../RouteHandlers/RouteHandler";
import { SaveQueryPane } from "./Panes/SaveQueryPane"; import { SaveQueryPane } from "./Panes/SaveQueryPane";
import { SettingsPane } from "./Panes/SettingsPane"; import { SettingsPane } from "./Panes/SettingsPane";
import { SetupNotebooksPane } from "./Panes/SetupNotebooksPane"; import { SetupNotebooksPane } from "./Panes/SetupNotebooksPane";
import { SparkClusterManager } from "../SparkClusterManager/SparkClusterManager";
import { SplashScreenComponentAdapter } from "./SplashScreen/SplashScreenComponentApdapter"; import { SplashScreenComponentAdapter } from "./SplashScreen/SplashScreenComponentApdapter";
import { Splitter, SplitterBounds, SplitterDirection } from "../Common/Splitter"; import { Splitter, SplitterBounds, SplitterDirection } from "../Common/Splitter";
import { StringInputPane } from "./Panes/StringInputPane"; import { StringInputPane } from "./Panes/StringInputPane";
@@ -83,6 +79,9 @@ import { UploadItemsPane } from "./Panes/UploadItemsPane";
import { UploadItemsPaneAdapter } from "./Panes/UploadItemsPaneAdapter"; import { UploadItemsPaneAdapter } from "./Panes/UploadItemsPaneAdapter";
import { ReactAdapter } from "../Bindings/ReactBindingHandler"; import { ReactAdapter } from "../Bindings/ReactBindingHandler";
import { toRawContentUri, fromContentUri } from "../Utils/GitHubUtils"; import { toRawContentUri, fromContentUri } from "../Utils/GitHubUtils";
import UserDefinedFunction from "./Tree/UserDefinedFunction";
import StoredProcedure from "./Tree/StoredProcedure";
import Trigger from "./Tree/Trigger";
BindingHandlersRegisterer.registerBindingHandlers(); BindingHandlersRegisterer.registerBindingHandlers();
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import // Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
@@ -93,7 +92,7 @@ enum ShareAccessToggleState {
Read Read
} }
export default class Explorer implements ViewModels.Explorer { export default class Explorer {
public flight: ko.Observable<string> = ko.observable<string>( public flight: ko.Observable<string> = ko.observable<string>(
SharedConstants.CollectionCreation.DefaultAddCollectionDefaultFlight SharedConstants.CollectionCreation.DefaultAddCollectionDefaultFlight
); );
@@ -168,30 +167,28 @@ export default class Explorer implements ViewModels.Explorer {
public tabsManager: TabsManager; public tabsManager: TabsManager;
// Contextual panes // Contextual panes
public addDatabasePane: ViewModels.AddDatabasePane; public addDatabasePane: AddDatabasePane;
public addCollectionPane: ViewModels.AddCollectionPane; public addCollectionPane: AddCollectionPane;
public deleteCollectionConfirmationPane: ViewModels.DeleteCollectionConfirmationPane; public deleteCollectionConfirmationPane: DeleteCollectionConfirmationPane;
public deleteDatabaseConfirmationPane: ViewModels.DeleteDatabaseConfirmationPane; public deleteDatabaseConfirmationPane: DeleteDatabaseConfirmationPane;
public graphStylingPane: ViewModels.GraphStylingPane; public graphStylingPane: GraphStylingPane;
public addTableEntityPane: ViewModels.AddTableEntityPane; public addTableEntityPane: AddTableEntityPane;
public editTableEntityPane: ViewModels.EditTableEntityPane; public editTableEntityPane: EditTableEntityPane;
public tableColumnOptionsPane: TableColumnOptionsPane; public tableColumnOptionsPane: TableColumnOptionsPane;
public querySelectPane: QuerySelectPane; public querySelectPane: QuerySelectPane;
public newVertexPane: ViewModels.NewVertexPane; public newVertexPane: NewVertexPane;
public cassandraAddCollectionPane: ViewModels.CassandraAddCollectionPane; public cassandraAddCollectionPane: CassandraAddCollectionPane;
public settingsPane: ViewModels.SettingsPane; public settingsPane: SettingsPane;
public executeSprocParamsPane: ViewModels.ExecuteSprocParamsPane; public executeSprocParamsPane: ExecuteSprocParamsPane;
public renewAdHocAccessPane: ViewModels.RenewAdHocAccessPane; public renewAdHocAccessPane: RenewAdHocAccessPane;
public uploadItemsPane: ViewModels.UploadItemsPane; public uploadItemsPane: UploadItemsPane;
public uploadItemsPaneAdapter: UploadItemsPaneAdapter; public uploadItemsPaneAdapter: UploadItemsPaneAdapter;
public loadQueryPane: ViewModels.LoadQueryPane; public loadQueryPane: LoadQueryPane;
public saveQueryPane: ViewModels.ContextualPane; public saveQueryPane: ViewModels.ContextualPane;
public browseQueriesPane: ViewModels.BrowseQueriesPane; public browseQueriesPane: BrowseQueriesPane;
public uploadFilePane: UploadFilePane; public uploadFilePane: UploadFilePane;
public stringInputPane: StringInputPane; public stringInputPane: StringInputPane;
public setupNotebooksPane: SetupNotebooksPane; public setupNotebooksPane: SetupNotebooksPane;
public libraryManagePane: ViewModels.ContextualPane;
public clusterLibraryPane: ViewModels.ContextualPane;
public gitHubReposPane: ViewModels.ContextualPane; public gitHubReposPane: ViewModels.ContextualPane;
public publishNotebookPaneAdapter: ReactAdapter; public publishNotebookPaneAdapter: ReactAdapter;
@@ -206,7 +203,7 @@ export default class Explorer implements ViewModels.Explorer {
public shouldShowShareDialogContents: ko.Observable<boolean>; public shouldShowShareDialogContents: ko.Observable<boolean>;
public shareAccessData: ko.Observable<ViewModels.AdHocAccessData>; 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 renewTokenError: ko.Observable<string>;
public tokenForRenewal: ko.Observable<string>; public tokenForRenewal: ko.Observable<string>;
public shareAccessToggleState: ko.Observable<ShareAccessToggleState>; public shareAccessToggleState: ko.Observable<ShareAccessToggleState>;
@@ -220,8 +217,7 @@ export default class Explorer implements ViewModels.Explorer {
public isNotebookEnabled: ko.Observable<boolean>; public isNotebookEnabled: ko.Observable<boolean>;
public isNotebooksEnabledForAccount: ko.Observable<boolean>; public isNotebooksEnabledForAccount: ko.Observable<boolean>;
public notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>; public notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>;
public notebookWorkspaceManager: ViewModels.NotebookWorkspaceManager; public notebookWorkspaceManager: NotebookWorkspaceManager;
public sparkClusterManager: ViewModels.SparkClusterManager;
public sparkClusterConnectionInfo: ko.Observable<DataModels.SparkClusterConnectionInfo>; public sparkClusterConnectionInfo: ko.Observable<DataModels.SparkClusterConnectionInfo>;
public isSparkEnabled: ko.Observable<boolean>; public isSparkEnabled: ko.Observable<boolean>;
public isSparkEnabledForAccount: ko.Observable<boolean>; public isSparkEnabledForAccount: ko.Observable<boolean>;
@@ -238,7 +234,7 @@ export default class Explorer implements ViewModels.Explorer {
private _isInitializingNotebooks: boolean; private _isInitializingNotebooks: boolean;
private _isInitializingSparkConnectionInfo: boolean; private _isInitializingSparkConnectionInfo: boolean;
private notebookBasePath: ko.Observable<string>; private notebookBasePath: ko.Observable<string>;
private _arcadiaManager: ViewModels.ArcadiaResourceManager; private _arcadiaManager: ArcadiaResourceManager;
private notebookToImport: { private notebookToImport: {
name: string; name: string;
content: string; content: string;
@@ -313,7 +309,6 @@ export default class Explorer implements ViewModels.Explorer {
this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(true); this.isAuthWithResourceToken() ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(true);
RouteHandler.getInstance().initHandler(); RouteHandler.getInstance().initHandler();
this.notebookWorkspaceManager = new NotebookWorkspaceManager(this.armEndpoint()); this.notebookWorkspaceManager = new NotebookWorkspaceManager(this.armEndpoint());
this.sparkClusterManager = new SparkClusterManager(this.armEndpoint());
this.arcadiaWorkspaces = ko.observableArray(); this.arcadiaWorkspaces = ko.observableArray();
this._arcadiaManager = new ArcadiaResourceManager(this.armEndpoint()); this._arcadiaManager = new ArcadiaResourceManager(this.armEndpoint());
this._isAfecFeatureRegistered(Constants.AfecFeatures.StorageAnalytics).then(isRegistered => this._isAfecFeatureRegistered(Constants.AfecFeatures.StorageAnalytics).then(isRegistered =>
@@ -750,22 +745,6 @@ export default class Explorer implements ViewModels.Explorer {
container: this 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.tabsManager = new TabsManager();
this._panes = [ this._panes = [
@@ -1607,70 +1586,6 @@ export default class Explorer implements ViewModels.Explorer {
window.open(Constants.Urls.feedbackEmail, "_self"); 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> { public async getArcadiaToken(): Promise<string> {
return new Promise<string>((resolve: (token: string) => void, reject: (error: any) => void) => { return new Promise<string>((resolve: (token: string) => void, reject: (error: any) => void) => {
MessageHandler.sendCachedDataMessage<string>(MessageTypes.GetArcadiaToken, undefined /** params **/).then( 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 () => { private _resetNotebookWorkspace = async () => {
this._closeModalDialog(); this._closeModalDialog();
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Resetting notebook workspace"); 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 // 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(); const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
return _.find(selectedCollection.storedProcedures(), (storedProcedure: ViewModels.StoredProcedure) => { return _.find(selectedCollection.storedProcedures(), (storedProcedure: StoredProcedure) => {
const openedSprocTab = this.tabsManager.getTabs( const openedSprocTab = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.StoredProcedures, ViewModels.CollectionTabKind.StoredProcedures,
(tab: ViewModels.Tab) => tab.node && tab.node.rid === storedProcedure.rid (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(); const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
return _.find(selectedCollection.userDefinedFunctions(), (userDefinedFunction: ViewModels.UserDefinedFunction) => { return _.find(selectedCollection.userDefinedFunctions(), (userDefinedFunction: UserDefinedFunction) => {
const openedUdfTab = this.tabsManager.getTabs( const openedUdfTab = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.UserDefinedFunctions, ViewModels.CollectionTabKind.UserDefinedFunctions,
(tab: ViewModels.Tab) => tab.node && tab.node.rid === userDefinedFunction.rid (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(); const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
return _.find(selectedCollection.triggers(), (trigger: ViewModels.Trigger) => { return _.find(selectedCollection.triggers(), (trigger: Trigger) => {
const openedTriggerTab = this.tabsManager.getTabs( const openedTriggerTab = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.Triggers, ViewModels.CollectionTabKind.Triggers,
(tab: ViewModels.Tab) => tab.node && tab.node.rid === trigger.rid (tab: ViewModels.Tab) => tab.node && tab.node.rid === trigger.rid
@@ -2835,42 +2703,6 @@ export default class Explorer implements ViewModels.Explorer {
return false; 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> => { private refreshNotebookList = async (): Promise<void> => {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) { if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
return; return;

View File

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

View File

@@ -1,24 +1,25 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as ViewModels from "../../../Contracts/ViewModels";
import { CommandBarComponentButtonFactory } from "./CommandBarComponentButtonFactory"; import { CommandBarComponentButtonFactory } from "./CommandBarComponentButtonFactory";
import { ExplorerStub } from "../../OpenActionsStubs";
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService"; import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
import NotebookManager from "../../Notebook/NotebookManager"; import NotebookManager from "../../Notebook/NotebookManager";
import Explorer from "../../Explorer";
describe("CommandBarComponentButtonFactory tests", () => { describe("CommandBarComponentButtonFactory tests", () => {
let mockExplorer: ViewModels.Explorer; let mockExplorer: Explorer;
describe("Enable notebook button", () => { describe("Enable notebook button", () => {
const enableNotebookBtnLabel = "Enable Notebooks (Preview)"; const enableNotebookBtnLabel = "Enable Notebooks (Preview)";
beforeAll(() => { beforeAll(() => {
mockExplorer = new ExplorerStub(); mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText"); mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false); mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true); mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false); mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false); mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false); mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true); mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
@@ -75,12 +76,13 @@ describe("CommandBarComponentButtonFactory tests", () => {
const openMongoShellBtnLabel = "Open Mongo Shell"; const openMongoShellBtnLabel = "Open Mongo Shell";
beforeAll(() => { beforeAll(() => {
mockExplorer = new ExplorerStub(); mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText"); mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false); mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true); mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false); mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false); mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true); mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
@@ -155,11 +157,12 @@ describe("CommandBarComponentButtonFactory tests", () => {
const openCassandraShellBtnLabel = "Open Cassandra Shell"; const openCassandraShellBtnLabel = "Open Cassandra Shell";
beforeAll(() => { beforeAll(() => {
mockExplorer = new ExplorerStub(); mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText"); mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false); mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true); mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false); mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false); mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true); mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
@@ -236,13 +239,14 @@ describe("CommandBarComponentButtonFactory tests", () => {
const manageGitHubSettingsBtnLabel = "Manage GitHub settings"; const manageGitHubSettingsBtnLabel = "Manage GitHub settings";
beforeAll(() => { beforeAll(() => {
mockExplorer = new ExplorerStub(); mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText"); mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(false); mockExplorer.isAuthWithResourceToken = ko.observable(false);
mockExplorer.isPreferredApiTable = ko.computed(() => true); mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false); mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false); mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true); mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
mockExplorer.isSparkEnabled = ko.observable(true); mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true; mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false); mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
@@ -294,7 +298,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
describe("Resource token", () => { describe("Resource token", () => {
beforeAll(() => { beforeAll(() => {
mockExplorer = new ExplorerStub(); mockExplorer = {} as Explorer;
mockExplorer.addCollectionText = ko.observable("mockText"); mockExplorer.addCollectionText = ko.observable("mockText");
mockExplorer.isAuthWithResourceToken = ko.observable(true); mockExplorer.isAuthWithResourceToken = ko.observable(true);
mockExplorer.isPreferredApiDocumentDB = ko.computed(() => 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 GitHubIcon from "../../../../images/github.svg";
import SynapseIcon from "../../../../images/synapse-link.svg"; import SynapseIcon from "../../../../images/synapse-link.svg";
import { config, Platform } from "../../../Config"; import { config, Platform } from "../../../Config";
import Explorer from "../../Explorer";
export class CommandBarComponentButtonFactory { export class CommandBarComponentButtonFactory {
private static counter: number = 0; private static counter: number = 0;
public static createStaticCommandBarButtons(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig[] { public static createStaticCommandBarButtons(container: Explorer): ViewModels.NavbarButtonConfig[] {
if (container.isAuthWithResourceToken()) { if (container.isAuthWithResourceToken()) {
return CommandBarComponentButtonFactory.createStaticCommandBarButtonsForResourceToken(container); return CommandBarComponentButtonFactory.createStaticCommandBarButtonsForResourceToken(container);
} }
@@ -132,7 +133,7 @@ export class CommandBarComponentButtonFactory {
return buttons; return buttons;
} }
public static createContextCommandBarButtons(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig[] { public static createContextCommandBarButtons(container: Explorer): ViewModels.NavbarButtonConfig[] {
const buttons: ViewModels.NavbarButtonConfig[] = []; const buttons: ViewModels.NavbarButtonConfig[] = [];
if (!container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()) { if (!container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()) {
@@ -155,7 +156,7 @@ export class CommandBarComponentButtonFactory {
return buttons; return buttons;
} }
public static createControlCommandBarButtons(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig[] { public static createControlCommandBarButtons(container: Explorer): ViewModels.NavbarButtonConfig[] {
const buttons: ViewModels.NavbarButtonConfig[] = []; const buttons: ViewModels.NavbarButtonConfig[] = [];
if (window.dataExplorerPlatform === PlatformType.Hosted) { if (window.dataExplorerPlatform === PlatformType.Hosted) {
return buttons; 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(); return container.isPreferredApiDocumentDB() || container.isPreferredApiGraph();
} }
private static createNewCollectionGroup(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig { private static createNewCollectionGroup(container: Explorer): ViewModels.NavbarButtonConfig {
const label = container.addCollectionText(); const label = container.addCollectionText();
return { return {
iconSrc: AddCollectionIcon, 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) { if (config.platform === Platform.Emulator) {
return null; 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(); const label = container.addDatabaseText();
return { return {
iconSrc: AddDatabaseIcon, 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()) { if (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) {
const label = "New SQL Query"; const label = "New SQL Query";
return { return {
@@ -324,7 +325,7 @@ export class CommandBarComponentButtonFactory {
return null; return null;
} }
public static createScriptCommandButtons(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig[] { public static createScriptCommandButtons(container: Explorer): ViewModels.NavbarButtonConfig[] {
const buttons: ViewModels.NavbarButtonConfig[] = []; const buttons: ViewModels.NavbarButtonConfig[] = [];
const shouldEnableScriptsCommands: boolean = const shouldEnableScriptsCommands: boolean =
@@ -384,7 +385,7 @@ export class CommandBarComponentButtonFactory {
return buttons; return buttons;
} }
private static createScaleAndSettingsButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig { private static createScaleAndSettingsButton(container: Explorer): ViewModels.NavbarButtonConfig {
let isShared = false; let isShared = false;
if (container.isDatabaseNodeSelected()) { if (container.isDatabaseNodeSelected()) {
isShared = container.findSelectedDatabase().isDatabaseShared(); 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"; const label = "New Notebook";
return { return {
iconSrc: NewNotebookIcon, 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"; const label = "Upload to Notebook Server";
return { return {
iconSrc: NewNotebookIcon, 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"; const label = "Open Query";
return { return {
iconSrc: BrowseQueriesIcon, 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"; const label = "Open Query From Disk";
return { return {
iconSrc: OpenQueryFromDiskIcon, 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) { if (config.platform === Platform.Emulator) {
return null; 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"; const label = "Open Terminal";
return { return {
iconSrc: CosmosTerminalIcon, 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 label = "Open Mongo Shell";
const tooltip = const tooltip =
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."; "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 label = "Open Cassandra Shell";
const tooltip = const tooltip =
"This feature is not yet available in your account's region. View supported regions here: https://aka.ms/cosmos-enable-notebooks."; "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"; const label = "Reset Workspace";
return { return {
iconSrc: ResetWorkspaceIcon, 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(); let connectedToGitHub: boolean = container.notebookManager?.gitHubOAuthService.isLoggedIn();
const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub"; const label = connectedToGitHub ? "Manage GitHub settings" : "Connect to GitHub";
return { return {
@@ -583,9 +584,7 @@ export class CommandBarComponentButtonFactory {
}; };
} }
private static createStaticCommandBarButtonsForResourceToken( private static createStaticCommandBarButtonsForResourceToken(container: Explorer): ViewModels.NavbarButtonConfig[] {
container: ViewModels.Explorer
): ViewModels.NavbarButtonConfig[] {
const newSqlQueryBtn = CommandBarComponentButtonFactory.createNewSQLQueryButton(container); const newSqlQueryBtn = CommandBarComponentButtonFactory.createNewSQLQueryButton(container);
const openQueryBtn = CommandBarComponentButtonFactory.createOpenQueryButton(container); const openQueryBtn = CommandBarComponentButtonFactory.createOpenQueryButton(container);

View File

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

View File

@@ -3,6 +3,7 @@ import { StorageKey, LocalStorageUtility } from "../../Shared/StorageUtility";
import CollectionIcon from "../../../images/tree-collection.svg"; import CollectionIcon from "../../../images/tree-collection.svg";
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg"; import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
import Explorer from "../Explorer";
export enum Type { export enum Type {
OpenCollection, OpenCollection,
@@ -36,7 +37,7 @@ export class MostRecentActivity {
private static readonly schemaVersion: string = "1"; private static readonly schemaVersion: string = "1";
private static itemsMaxNumber: number = 5; private static itemsMaxNumber: number = 5;
private storedData: StoredData; private storedData: StoredData;
constructor(private container: ViewModels.Explorer) { constructor(private container: Explorer) {
// Retrieve from local storage // Retrieve from local storage
if (LocalStorageUtility.hasItem(StorageKey.MostRecentActivity)) { if (LocalStorageUtility.hasItem(StorageKey.MostRecentActivity)) {
const rawData = LocalStorageUtility.getEntryString(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> { private async recreateNotebookWorkspaceAsync(): Promise<void> {
const explorer = window.dataExplorer as ViewModels.Explorer; const explorer = window.dataExplorer;
if (!explorer || !explorer.databaseAccount() || !explorer.databaseAccount().id) { if (!explorer || !explorer.databaseAccount() || !explorer.databaseAccount().id) {
throw new Error("DataExplorer not initialized"); throw new Error("DataExplorer not initialized");
} }

View File

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

View File

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

View File

@@ -2,11 +2,12 @@
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { ActionContracts } from "../Contracts/ExplorerContracts"; import { ActionContracts } from "../Contracts/ExplorerContracts";
import Explorer from "./Explorer";
export function handleOpenAction( export function handleOpenAction(
action: ActionContracts.DataExplorerAction, action: ActionContracts.DataExplorerAction,
databases: ViewModels.Database[], databases: ViewModels.Database[],
explorer: ViewModels.Explorer explorer: Explorer
): boolean { ): boolean {
if ( if (
action.actionType === ActionContracts.ActionType.OpenCollectionTab || 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 ( if (
action.paneKind === ActionContracts.PaneKind.AddCollection || action.paneKind === ActionContracts.PaneKind.AddCollection ||
(<any>action).paneKind === ActionContracts.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)); 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("Add Collection Pane", () => {
describe("isValid()", () => { describe("isValid()", () => {
let explorer: ViewModels.Explorer; let explorer: Explorer;
const mockDatabaseAccount: ViewModels.DatabaseAccount = { const mockDatabaseAccount: ViewModels.DatabaseAccount = {
id: "mock", id: "mock",
kind: "DocumentDB", kind: "DocumentDB",

View File

@@ -21,7 +21,7 @@ import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
import { HashMap } from "../../Common/HashMap"; import { HashMap } from "../../Common/HashMap";
import { PlatformType } from "../../PlatformType"; 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 defaultExperience: ko.Computed<string>;
public databaseIds: ko.ObservableArray<string>; public databaseIds: ko.ObservableArray<string>;
public collectionId: ko.Observable<string>; public collectionId: ko.Observable<string>;

View File

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

View File

@@ -17,7 +17,7 @@ import { ContextualPaneBase } from "./ContextualPaneBase";
import { CosmosClient } from "../../Common/CosmosClient"; import { CosmosClient } from "../../Common/CosmosClient";
import { PlatformType } from "../../PlatformType"; 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 defaultExperience: ko.Computed<string>;
public databaseIdLabel: ko.Computed<string>; public databaseIdLabel: ko.Computed<string>;
public databaseId: ko.Observable<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 { QueriesGridComponentAdapter } from "../Controls/QueriesGridReactComponent/QueriesGridComponentAdapter";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
export class BrowseQueriesPane extends ContextualPaneBase implements ViewModels.BrowseQueriesPane { export class BrowseQueriesPane extends ContextualPaneBase {
public queriesGridComponentAdapter: QueriesGridComponentAdapter; public queriesGridComponentAdapter: QueriesGridComponentAdapter;
public canSaveQueries: ko.Computed<boolean>; public canSaveQueries: ko.Computed<boolean>;

View File

@@ -13,8 +13,7 @@ import { CassandraAPIDataClient } from "../Tables/TableDataClient";
import { ContextualPaneBase } from "./ContextualPaneBase"; import { ContextualPaneBase } from "./ContextualPaneBase";
import { HashMap } from "../../Common/HashMap"; import { HashMap } from "../../Common/HashMap";
export default class CassandraAddCollectionPane extends ContextualPaneBase export default class CassandraAddCollectionPane extends ContextualPaneBase {
implements ViewModels.CassandraAddCollectionPane {
public createTableQuery: ko.Observable<string>; public createTableQuery: ko.Observable<string>;
public keyspaceId: ko.Observable<string>; public keyspaceId: ko.Observable<string>;
public maxThroughputRU: ko.Observable<number>; 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 ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
@@ -7,11 +6,12 @@ import { KeyCodes } from "../../Common/Constants";
import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel"; import { WaitsForTemplateViewModel } from "../WaitsForTemplateViewModel";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import DocumentClientUtilityBase from "../../Common/DocumentClientUtilityBase"; import DocumentClientUtilityBase from "../../Common/DocumentClientUtilityBase";
import Explorer from "../Explorer";
// TODO: Use specific actions for logging telemetry data // TODO: Use specific actions for logging telemetry data
export abstract class ContextualPaneBase extends WaitsForTemplateViewModel implements ViewModels.ContextualPane { export abstract class ContextualPaneBase extends WaitsForTemplateViewModel implements ViewModels.ContextualPane {
public id: string; public id: string;
public container: ViewModels.Explorer; public container: Explorer;
public firstFieldHasFocus: ko.Observable<boolean>; public firstFieldHasFocus: ko.Observable<boolean>;
public formErrorsDetails: ko.Observable<string>; public formErrorsDetails: ko.Observable<string>;
public formErrors: 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 DocumentClientUtilityBase from "../../Common/DocumentClientUtilityBase";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { CollectionStub, DatabaseStub, ExplorerStub } from "../OpenActionsStubs";
import { TreeNode } from "../../Contracts/ViewModels"; import { TreeNode } from "../../Contracts/ViewModels";
describe("Delete Collection Confirmation Pane", () => { describe("Delete Collection Confirmation Pane", () => {
describe("Explorer.isLastCollection()", () => { describe("Explorer.isLastCollection()", () => {
let explorer: ViewModels.Explorer; let explorer: Explorer;
beforeEach(() => { beforeEach(() => {
explorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false }); explorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false });
}); });
it("should be true if 1 database and 1 collection", () => { it("should be true if 1 database and 1 collection", () => {
let database: ViewModels.Database = new DatabaseStub({}); let database = {} as ViewModels.Database;
database.collections = ko.observableArray<ViewModels.Collection>([new CollectionStub({})]); database.collections = ko.observableArray<ViewModels.Collection>([{} as ViewModels.Collection]);
explorer.databases = ko.observableArray<ViewModels.Database>([database]); explorer.databases = ko.observableArray<ViewModels.Database>([database]);
expect(explorer.isLastCollection()).toBe(true); expect(explorer.isLastCollection()).toBe(true);
}); });
it("should be false if if 1 database and 2 collection", () => { 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>([ database.collections = ko.observableArray<ViewModels.Collection>([
new CollectionStub({}), {} as ViewModels.Collection,
new CollectionStub({}) {} as ViewModels.Collection
]); ]);
explorer.databases = ko.observableArray<ViewModels.Database>([database]); explorer.databases = ko.observableArray<ViewModels.Database>([database]);
expect(explorer.isLastCollection()).toBe(false); expect(explorer.isLastCollection()).toBe(false);
}); });
it("should be false if 2 database and 1 collection each", () => { it("should be false if 2 database and 1 collection each", () => {
let database: ViewModels.Database = new DatabaseStub({}); let database = {} as ViewModels.Database;
database.collections = ko.observableArray<ViewModels.Collection>([new CollectionStub({})]); database.collections = ko.observableArray<ViewModels.Collection>([{} as ViewModels.Collection]);
let database2: ViewModels.Database = new DatabaseStub({}); let database2 = {} as ViewModels.Database;
database2.collections = ko.observableArray<ViewModels.Collection>([new CollectionStub({})]); database2.collections = ko.observableArray<ViewModels.Collection>([{} as ViewModels.Collection]);
explorer.databases = ko.observableArray<ViewModels.Database>([database, database2]); explorer.databases = ko.observableArray<ViewModels.Database>([database, database2]);
expect(explorer.isLastCollection()).toBe(false); expect(explorer.isLastCollection()).toBe(false);
}); });
it("should be false if 0 databases", () => { it("should be false if 0 databases", () => {
let database: ViewModels.Database = new DatabaseStub({}); let database = {} as ViewModels.Database;
explorer.databases = ko.observableArray<ViewModels.Database>(); explorer.databases = ko.observableArray<ViewModels.Database>();
database.collections = ko.observableArray<ViewModels.Collection>(); database.collections = ko.observableArray<ViewModels.Collection>();
expect(explorer.isLastCollection()).toBe(false); expect(explorer.isLastCollection()).toBe(false);
@@ -59,29 +58,27 @@ describe("Delete Collection Confirmation Pane", () => {
let fakeDocumentClientUtility = sinon.createStubInstance<DocumentClientUtilityBase>( let fakeDocumentClientUtility = sinon.createStubInstance<DocumentClientUtilityBase>(
DocumentClientUtilityBase as any DocumentClientUtilityBase as any
); );
let fakeExplorer = sinon.createStubInstance<ExplorerStub>(ExplorerStub as any); let fakeExplorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false });
sinon.stub(fakeExplorer, "isNotificationConsoleExpanded").value(ko.observable<boolean>(false)); fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
fakeExplorer.refreshAllDatabases = () => Q.resolve();
let pane = new DeleteCollectionConfirmationPane({ let pane = new DeleteCollectionConfirmationPane({
documentClientUtility: fakeDocumentClientUtility as any, documentClientUtility: fakeDocumentClientUtility as any,
id: "deletecollectionconfirmationpane", id: "deletecollectionconfirmationpane",
visible: ko.observable<boolean>(false), visible: ko.observable<boolean>(false),
container: fakeExplorer as any container: fakeExplorer
}); });
fakeExplorer.isLastCollection.returns(true); fakeExplorer.isLastCollection = () => true;
fakeExplorer.isSelectedDatabaseShared.returns(false); fakeExplorer.isSelectedDatabaseShared = () => false;
pane.container = fakeExplorer as any;
expect(pane.shouldRecordFeedback()).toBe(true); expect(pane.shouldRecordFeedback()).toBe(true);
fakeExplorer.isLastCollection.returns(true); fakeExplorer.isLastCollection = () => true;
fakeExplorer.isSelectedDatabaseShared.returns(true); fakeExplorer.isSelectedDatabaseShared = () => true;
pane.container = fakeExplorer as any;
expect(pane.shouldRecordFeedback()).toBe(false); expect(pane.shouldRecordFeedback()).toBe(false);
fakeExplorer.isLastCollection.returns(false); fakeExplorer.isLastCollection = () => false;
fakeExplorer.isSelectedDatabaseShared.returns(false); fakeExplorer.isSelectedDatabaseShared = () => false;
pane.container = fakeExplorer as any;
expect(pane.shouldRecordFeedback()).toBe(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", () => { it("it should log feedback if last collection and database is not shared", () => {
let selectedCollectionId = "testCol"; let selectedCollectionId = "testCol";
let fakeDocumentClientUtility = sinon.createStubInstance<DocumentClientUtilityBase>( let fakeDocumentClientUtility = {} as DocumentClientUtilityBase;
DocumentClientUtilityBase as any fakeDocumentClientUtility.deleteCollection = () => Q(null);
); let fakeExplorer = {} as Explorer;
fakeDocumentClientUtility.deleteCollection.returns(Q.resolve(null)); fakeExplorer.findSelectedCollection = () => {
let fakeExplorer = sinon.createStubInstance<ExplorerStub>(ExplorerStub as any); return {
fakeExplorer.findSelectedCollection.returns(
new CollectionStub({
id: ko.observable<string>(selectedCollectionId), id: ko.observable<string>(selectedCollectionId),
rid: "test" rid: "test"
}) } as ViewModels.Collection;
); };
sinon.stub(fakeExplorer, "isNotificationConsoleExpanded").value(ko.observable<boolean>(false)); fakeExplorer.isNotificationConsoleExpanded = ko.observable<boolean>(false);
sinon.stub(fakeExplorer, "selectedCollectionId").value(ko.observable<string>(selectedCollectionId)); fakeExplorer.selectedCollectionId = ko.computed<string>(() => selectedCollectionId);
fakeExplorer.isSelectedDatabaseShared.returns(false); fakeExplorer.isSelectedDatabaseShared = () => false;
const SubscriptionId = "testId"; const SubscriptionId = "testId";
const AccountName = "testAccount"; const AccountName = "testAccount";
sinon.stub(fakeExplorer, "databaseAccount").value( fakeExplorer.databaseAccount = ko.observable<ViewModels.DatabaseAccount>({
ko.observable<ViewModels.DatabaseAccount>({ id: SubscriptionId,
id: SubscriptionId, name: AccountName
name: AccountName } as ViewModels.DatabaseAccount);
} as ViewModels.DatabaseAccount)
); fakeExplorer.defaultExperience = ko.observable<string>("DocumentDB");
sinon.stub(fakeExplorer, "defaultExperience").value(ko.observable<string>("DocumentDB")); fakeExplorer.isPreferredApiCassandra = ko.computed(() => {
sinon.stub(fakeExplorer, "isPreferredApiCassandra").value( return false;
ko.computed(() => { });
return false;
}) fakeExplorer.documentClientUtility = fakeDocumentClientUtility;
); fakeExplorer.selectedNode = ko.observable<TreeNode>();
sinon.stub(fakeExplorer, "documentClientUtility").value(fakeDocumentClientUtility); fakeExplorer.isLastCollection = () => true;
sinon.stub(fakeExplorer, "selectedNode").value(ko.observable<TreeNode>()); fakeExplorer.isSelectedDatabaseShared = () => false;
fakeExplorer.isLastCollection.returns(true); fakeExplorer.refreshAllDatabases = () => Q.resolve();
fakeExplorer.isSelectedDatabaseShared.returns(false);
let pane = new DeleteCollectionConfirmationPane({ let pane = new DeleteCollectionConfirmationPane({
documentClientUtility: fakeDocumentClientUtility as any, documentClientUtility: fakeDocumentClientUtility as any,

View File

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

View File

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

View File

@@ -13,8 +13,7 @@ import DeleteFeedback from "../../Common/DeleteFeedback";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
implements ViewModels.DeleteDatabaseConfirmationPane {
public databaseIdConfirmationText: ko.Observable<string>; public databaseIdConfirmationText: ko.Observable<string>;
public databaseIdConfirmation: ko.Observable<string>; public databaseIdConfirmation: ko.Observable<string>;
public databaseDeleteFeedback: 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 Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { ContextualPaneBase } from "./ContextualPaneBase"; import { ContextualPaneBase } from "./ContextualPaneBase";
import StoredProcedure from "../Tree/StoredProcedure";
export interface ExecuteSprocParam { export interface ExecuteSprocParam {
type: ko.Observable<string>; type: ko.Observable<string>;
@@ -14,7 +15,7 @@ type UnwrappedExecuteSprocParam = {
value: any; value: any;
}; };
export class ExecuteSprocParamsPane extends ContextualPaneBase implements ViewModels.ExecuteSprocParamsPane { export class ExecuteSprocParamsPane extends ContextualPaneBase {
public params: ko.ObservableArray<ExecuteSprocParam>; public params: ko.ObservableArray<ExecuteSprocParam>;
public partitionKeyType: ko.Observable<string>; public partitionKeyType: ko.Observable<string>;
public partitionKeyValue: 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 addNewParamLabel: string = "Add New Param";
public executeButtonEnabled: ko.Computed<boolean>; public executeButtonEnabled: ko.Computed<boolean>;
private _selectedSproc: ViewModels.StoredProcedure; private _selectedSproc: StoredProcedure;
constructor(options: ViewModels.PaneOptions) { constructor(options: ViewModels.PaneOptions) {
super(options); super(options);
@@ -39,8 +40,7 @@ export class ExecuteSprocParamsPane extends ContextualPaneBase implements ViewMo
public open() { public open() {
super.open(); super.open();
const currentSelectedSproc: ViewModels.StoredProcedure = const currentSelectedSproc = this.container && this.container.findSelectedStoredProcedure();
this.container && this.container.findSelectedStoredProcedure();
if (!!currentSelectedSproc && !!this._selectedSproc && this._selectedSproc.rid !== currentSelectedSproc.rid) { if (!!currentSelectedSproc && !!this._selectedSproc && this._selectedSproc.rid !== currentSelectedSproc.rid) {
this.params([]); this.params([]);
this.partitionKeyValue(""); this.partitionKeyValue("");

View File

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

View File

@@ -2,7 +2,7 @@ import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { ContextualPaneBase } from "./ContextualPaneBase"; import { ContextualPaneBase } from "./ContextualPaneBase";
export default class GraphStylingPane extends ContextualPaneBase implements ViewModels.GraphStylingPane { export default class GraphStylingPane extends ContextualPaneBase {
public graphConfigUIData: ViewModels.GraphConfigUiData; public graphConfigUIData: ViewModels.GraphConfigUiData;
private remoteConfig: 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 * as Logger from "../../Common/Logger";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
export class LoadQueryPane extends ContextualPaneBase implements ViewModels.LoadQueryPane { export class LoadQueryPane extends ContextualPaneBase {
public selectedFilesTitle: ko.Observable<string>; public selectedFilesTitle: ko.Observable<string>;
public files: ko.Observable<FileList>; public files: ko.Observable<FileList>;

View File

@@ -2,9 +2,10 @@ import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { ContextualPaneBase } from "./ContextualPaneBase"; import { ContextualPaneBase } from "./ContextualPaneBase";
import { KeyCodes } from "../../Common/Constants"; import { KeyCodes } from "../../Common/Constants";
import Explorer from "../Explorer";
export default class NewVertexPane extends ContextualPaneBase implements ViewModels.NewVertexPane { export default class NewVertexPane extends ContextualPaneBase {
public container: ViewModels.Explorer; public container: Explorer;
public visible: ko.Observable<boolean>; public visible: ko.Observable<boolean>;
public formErrors: ko.Observable<string>; public formErrors: ko.Observable<string>;
public formErrorsDetails: 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 UploadFilePaneTemplate from "./UploadFilePane.html";
import StringInputPaneTemplate from "./StringInputPane.html"; import StringInputPaneTemplate from "./StringInputPane.html";
import SetupNotebooksPaneTemplate from "./SetupNotebooksPane.html"; import SetupNotebooksPaneTemplate from "./SetupNotebooksPane.html";
import LibraryManagePaneTemplate from "./LibraryManagePane.html";
import ClusterLibraryPaneTemplate from "./ClusterLibraryPane.html";
import GitHubReposPaneTemplate from "./GitHubReposPane.html"; import GitHubReposPaneTemplate from "./GitHubReposPane.html";
export class PaneComponent { 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 { export class GitHubReposPaneComponent {
constructor() { constructor() {
return { return {

View File

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

View File

@@ -4,7 +4,7 @@ import Explorer from "../Explorer";
describe("Settings Pane", () => { describe("Settings Pane", () => {
describe("shouldShowQueryPageOptions()", () => { describe("shouldShowQueryPageOptions()", () => {
let explorer: ViewModels.Explorer; let explorer: Explorer;
beforeEach(() => { beforeEach(() => {
explorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false }); 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 { StringUtility } from "../../Shared/StringUtility";
import { config } from "../../Config"; import { config } from "../../Config";
export class SettingsPane extends ContextualPaneBase implements ViewModels.SettingsPane { export class SettingsPane extends ContextualPaneBase {
public pageOption: ko.Observable<string>; public pageOption: ko.Observable<string>;
public customItemPerPage: ko.Observable<number>; public customItemPerPage: ko.Observable<number>;
public crossPartitionQueryEnabled: ko.Observable<boolean>; public crossPartitionQueryEnabled: ko.Observable<boolean>;

View File

@@ -9,7 +9,7 @@ import * as Utilities from "../../Tables/Utilities";
import EntityPropertyViewModel from "./EntityPropertyViewModel"; import EntityPropertyViewModel from "./EntityPropertyViewModel";
import TableEntityPane from "./TableEntityPane"; 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 _excludedFields: string[] = [TableConstants.EntityKeyNames.Timestamp];
private static _readonlyFields: string[] = [ private static _readonlyFields: string[] = [

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler"; import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import { UploadDetailsRecord, UploadDetails } from "../../workers/upload/definitions"; import { UploadDetailsRecord, UploadDetails } from "../../workers/upload/definitions";
import InfoBubbleIcon from "../../../images/info-bubble.svg"; import InfoBubbleIcon from "../../../images/info-bubble.svg";
import Explorer from "../Explorer";
const UPLOAD_FILE_SIZE_LIMIT = 2097152; const UPLOAD_FILE_SIZE_LIMIT = 2097152;
@@ -23,7 +24,7 @@ export class UploadItemsPaneAdapter implements ReactAdapter {
private selectedFilesTitle: string; private selectedFilesTitle: string;
private uploadFileData: UploadDetailsRecord[]; private uploadFileData: UploadDetailsRecord[];
public constructor(private container: ViewModels.Explorer) { public constructor(private container: Explorer) {
this.parameters = ko.observable(Date.now()); this.parameters = ko.observable(Date.now());
this.reset(); this.reset();
this.triggerRender(); 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 AddDatabaseIcon from "../../../images/AddDatabase.svg";
import SampleIcon from "../../../images/Hero-sample.svg"; import SampleIcon from "../../../images/Hero-sample.svg";
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil"; import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
import Explorer from "../Explorer";
/** /**
* TODO Remove this when fully ported to ReactJS * TODO Remove this when fully ported to ReactJS
@@ -29,7 +30,7 @@ export class SplashScreenComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>; public parameters: ko.Observable<number>;
constructor(private container: ViewModels.Explorer) { constructor(private container: Explorer) {
this.parameters = ko.observable<number>(Date.now()); this.parameters = ko.observable<number>(Date.now());
this.container.tabsManager.openedTabs.subscribe((tabs: ViewModels.Tab[]) => { this.container.tabsManager.openedTabs.subscribe((tabs: ViewModels.Tab[]) => {
if (tabs.length === 0) { if (tabs.length === 0) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,6 +17,7 @@ import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { CosmosClient } from "../../Common/CosmosClient"; import { CosmosClient } from "../../Common/CosmosClient";
import { PlatformType } from "../../PlatformType"; import { PlatformType } from "../../PlatformType";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import Explorer from "../Explorer";
const updateThroughputBeyondLimitWarningMessage: string = ` const updateThroughputBeyondLimitWarningMessage: string = `
You are about to request an increase in throughput beyond the pre-allocated capacity. 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 /> This operation will take 1-3 business days to complete. View the latest status in Notifications.<br />
Database: ${databaseName}, ${currentThroughput(isAutoscale, throughput)}`; Database: ${databaseName}, ${currentThroughput(isAutoscale, throughput)}`;
export default class DatabaseSettingsTab extends TabsBase export default class DatabaseSettingsTab extends TabsBase implements ViewModels.WaitsForTemplate {
implements ViewModels.DatabaseSettingsTab, ViewModels.WaitsForTemplate {
// editables // editables
public isAutoPilotSelected: ViewModels.Editable<boolean>; public isAutoPilotSelected: ViewModels.Editable<boolean>;
public throughput: ViewModels.Editable<number>; public throughput: ViewModels.Editable<number>;
@@ -94,7 +94,7 @@ export default class DatabaseSettingsTab extends TabsBase
private _hasProvisioningTypeChanged: ko.Computed<boolean>; private _hasProvisioningTypeChanged: ko.Computed<boolean>;
private _wasAutopilotOriginallySet: ko.Observable<boolean>; private _wasAutopilotOriginallySet: ko.Observable<boolean>;
private _offerReplacePending: ko.Computed<boolean>; private _offerReplacePending: ko.Computed<boolean>;
private container: ViewModels.Explorer; private container: Explorer;
constructor(options: ViewModels.TabOptions) { constructor(options: ViewModels.TabOptions) {
super(options); 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 { extractPartitionKey, PartitionKeyDefinition, QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import Explorer from "../Explorer";
export default class DocumentsTab extends TabsBase implements ViewModels.DocumentsTab { export default class DocumentsTab extends TabsBase implements ViewModels.DocumentsTab {
public selectedDocumentId: ko.Observable<ViewModels.DocumentId>; 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"; const label = "Upload Item";
return { return {
iconSrc: UploadIcon, iconSrc: UploadIcon,

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ import {
NotebookViewerComponentProps NotebookViewerComponentProps
} from "../Controls/NotebookViewer/NotebookViewerComponent"; } from "../Controls/NotebookViewer/NotebookViewerComponent";
import TabsBase from "./TabsBase"; import TabsBase from "./TabsBase";
import Explorer from "../Explorer";
/** /**
* Notebook Viewer tab * Notebook Viewer tab
@@ -29,7 +30,7 @@ class NotebookViewerComponentAdapter implements ReactAdapter {
} }
export default class NotebookViewerTab extends TabsBase implements ViewModels.Tab { export default class NotebookViewerTab extends TabsBase implements ViewModels.Tab {
private container: ViewModels.Explorer; private container: Explorer;
public notebookUrl: string; public notebookUrl: string;
public notebookViewerComponentAdapter: NotebookViewerComponentAdapter; 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; return this.container;
} }

View File

@@ -2,21 +2,22 @@ import * as ko from "knockout";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import { CollectionStub, DatabaseStub } from "../../Explorer/OpenActionsStubs";
import QueryTab from "./QueryTab"; import QueryTab from "./QueryTab";
import { View } from "@nteract/data-explorer/lib/utilities/types";
import { PartitionKey } from "../../Contracts/DataModels";
describe("Query Tab", () => { describe("Query Tab", () => {
function getNewQueryTabForContainer(container: ViewModels.Explorer): ViewModels.QueryTab { function getNewQueryTabForContainer(container: Explorer): ViewModels.QueryTab {
const database: ViewModels.Database = new DatabaseStub({ const database = {
container: container, container: container,
id: ko.observable<string>("test"), id: ko.observable<string>("test"),
isDatabaseShared: () => false isDatabaseShared: () => false
}); } as ViewModels.Database;
const collection: ViewModels.Collection = new CollectionStub({ const collection = {
container: container, container: container,
databaseId: "test", databaseId: "test",
id: ko.observable<string>("test") id: ko.observable<string>("test")
}); } as ViewModels.Collection;
return new QueryTab({ return new QueryTab({
tabKind: ViewModels.CollectionTabKind.Query, tabKind: ViewModels.CollectionTabKind.Query,
@@ -33,19 +34,12 @@ describe("Query Tab", () => {
} }
describe("shouldSetSystemPartitionKeyContainerPartitionKeyValueUndefined", () => { describe("shouldSetSystemPartitionKeyContainerPartitionKeyValueUndefined", () => {
const collection: ViewModels.Collection = new CollectionStub({ const collection = {
id: "withoutsystempk", id: ko.observable<string>("withoutsystempk"),
partitionKey: { partitionKey: {
systemKey: true systemKey: true
} }
}); } as ViewModels.Collection;
const collectionSystemPK: ViewModels.Collection = new CollectionStub({
id: "withsystempk",
partitionKey: {
systemKey: true
}
});
it("no container with system pk, should not set partition key option", () => { it("no container with system pk, should not set partition key option", () => {
const iteratorOptions = QueryTab.getIteratorOptions(collection); const iteratorOptions = QueryTab.getIteratorOptions(collection);
@@ -54,7 +48,7 @@ describe("Query Tab", () => {
}); });
describe("isQueryMetricsEnabled()", () => { describe("isQueryMetricsEnabled()", () => {
let explorer: ViewModels.Explorer; let explorer: Explorer;
beforeEach(() => { beforeEach(() => {
explorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false }); explorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false });
@@ -74,7 +68,7 @@ describe("Query Tab", () => {
}); });
describe("Save Queries command button", () => { describe("Save Queries command button", () => {
let explorer: ViewModels.Explorer; let explorer: Explorer;
beforeEach(() => { beforeEach(() => {
explorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false }); 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>(() => { 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; 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 AddEntityIcon from "../../../images/AddEntity.svg";
import EditEntityIcon from "../../../images/Edit-entity.svg"; import EditEntityIcon from "../../../images/Edit-entity.svg";
import DeleteEntitiesIcon from "../../../images/DeleteEntities.svg"; import DeleteEntitiesIcon from "../../../images/DeleteEntities.svg";
import Explorer from "../Explorer";
// Will act as table explorer class // Will act as table explorer class
export default class QueryTablesTab extends TabsBase { export default class QueryTablesTab extends TabsBase {
@@ -31,7 +32,7 @@ export default class QueryTablesTab extends TabsBase {
public deleteEntityButton: ViewModels.Button; public deleteEntityButton: ViewModels.Button;
public queryBuilderButton: ViewModels.Button; public queryBuilderButton: ViewModels.Button;
public queryTextButton: ViewModels.Button; public queryTextButton: ViewModels.Button;
public container: ViewModels.Explorer; public container: Explorer;
constructor(options: ViewModels.TabOptions) { constructor(options: ViewModels.TabOptions) {
super(options); super(options);

View File

@@ -177,7 +177,7 @@ describe("Settings tab", () => {
}); });
describe("Should update collection", () => { describe("Should update collection", () => {
let explorer: ViewModels.Explorer; let explorer: Explorer;
beforeEach(() => { beforeEach(() => {
explorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false }); explorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false });
@@ -259,7 +259,7 @@ describe("Settings tab", () => {
}); });
describe("Get Conflict Resolution configuration from user", () => { describe("Get Conflict Resolution configuration from user", () => {
let explorer: ViewModels.Explorer; let explorer: Explorer;
beforeEach(() => { beforeEach(() => {
explorer = new Explorer({ documentClientUtility: null, notificationsClient: null, isEmulator: false }); 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 { CosmosClient } from "../../Common/CosmosClient";
import { PlatformType } from "../../PlatformType"; import { PlatformType } from "../../PlatformType";
import { RequestOptions } from "@azure/cosmos/dist-esm"; import { RequestOptions } from "@azure/cosmos/dist-esm";
import Explorer from "../Explorer";
const ttlWarning: string = ` 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. 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" 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 GEOGRAPHY: string = "Geography";
public GEOMETRY: string = "Geometry"; public GEOMETRY: string = "Geometry";
@@ -218,7 +219,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.Settings
public throughputModeRadioName: string; public throughputModeRadioName: string;
private _offerReplacePending: ko.PureComputed<boolean>; private _offerReplacePending: ko.PureComputed<boolean>;
private container: ViewModels.Explorer; private container: Explorer;
private _wasAutopilotOriginallySet: ko.Observable<boolean>; private _wasAutopilotOriginallySet: ko.Observable<boolean>;
private _isAutoPilotDirty: ko.Computed<boolean>; private _isAutoPilotDirty: ko.Computed<boolean>;
private _hasProvisioningTypeChanged: 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 DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import TabsBase from "./TabsBase"; import TabsBase from "./TabsBase";
import Explorer from "../Explorer";
export default class SparkMasterTab extends TabsBase { export default class SparkMasterTab extends TabsBase {
public sparkMasterSrc: ko.Observable<string>; public sparkMasterSrc: ko.Observable<string>;
private _clusterConnectionInfo: DataModels.SparkClusterConnectionInfo; private _clusterConnectionInfo: DataModels.SparkClusterConnectionInfo;
private _container: ViewModels.Explorer; private _container: Explorer;
constructor(options: ViewModels.SparkMasterTabOptions) { constructor(options: ViewModels.SparkMasterTabOptions) {
super(options); super(options);

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