Compare commits

..

34 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
Tanuj Mittal
b1e20796c2 Updates to standalone gallery (#91)
* Add hyperlink icon for links in DialogComponent

* Use a Cosmos DB image for sample Persona

* Fix tests

* Update standalone gallery behavior to match DE gallery

* Use /gallery endpoint for gallery

* Update Gallery Card style to remove buttons in standalone mode

* Address comments

* Add text banner
2020-07-15 13:58:43 -07:00
Srinath Narayanan
543ae9fe4a Added infinite progress bar when gallery read-only notebook viewer loads (#90)
* Added infinite progress bar when gallery notebook content loads

- reused infinite progress bar from magic commands

* made single quotes to double quotes

* formatting changes

* updated state

* changed to ProgressIndicator

* undo packgae.json change
2020-07-15 09:53:06 -07:00
Tanuj Mittal
db0b478eb0 Fix gallery card list bug (#87) 2020-07-15 09:27:27 -07:00
Steve Faulkner
f6938f5ec5 Add deployment status CLI utility (#86) 2020-07-15 07:49:06 -05:00
Steve Faulkner
9affc34301 Initial Pass at Accessibility Checks in CI (#88) 2020-07-14 23:01:28 -05:00
victor-meng
15953da51e Fix indexing off not working for SQL and graph free tier accounts (#85) 2020-07-13 10:54:59 -07:00
Steve Faulkner
15f9146ac9 Remove correlation iframe (#83)
Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
2020-07-13 09:41:41 -05:00
189 changed files with 12649 additions and 4238 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
@@ -271,7 +270,6 @@ src/Shared/AddCollectionUtility.test.ts
src/Shared/AddCollectionUtility.ts src/Shared/AddCollectionUtility.ts
src/Shared/AddDatabaseUtility.test.ts src/Shared/AddDatabaseUtility.test.ts
src/Shared/AddDatabaseUtility.ts src/Shared/AddDatabaseUtility.ts
src/Shared/Ajax.ts
src/Shared/Constants.ts src/Shared/Constants.ts
src/Shared/DefaultExperienceUtility.test.ts src/Shared/DefaultExperienceUtility.test.ts
src/Shared/DefaultExperienceUtility.ts src/Shared/DefaultExperienceUtility.ts
@@ -332,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

@@ -155,8 +155,31 @@ jobs:
NODE_TLS_REJECT_UNAUTHORIZED: 0 NODE_TLS_REJECT_UNAUTHORIZED: 0
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }} CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
accessibility:
name: "Accessibility | Hosted"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Accessibility Check
run: |
# Ubuntu gets mad when webpack runs too many files watchers
cat /proc/sys/fs/inotify/max_user_watches
sudo sysctl fs.inotify.max_user_watches=524288
sudo sysctl -p
npm ci
npm start &
npx wait-on -i 5000 https-get://0.0.0.0:1234/
node utils/accesibilityCheck.js
shell: bash
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
nuget: nuget:
name: Publish Nuget name: Publish Nuget
if: github.ref == 'refs/heads/master'
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo] needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
@@ -180,6 +203,7 @@ jobs:
path: "*.nupkg" path: "*.nupkg"
nugetmpac: nugetmpac:
name: Publish Nuget MPAC name: Publish Nuget MPAC
if: github.ref == 'refs/heads/master'
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo] needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:

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

182
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",
@@ -9230,6 +9268,21 @@
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
"integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug=="
}, },
"axe-core": {
"version": "3.5.5",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-3.5.5.tgz",
"integrity": "sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q==",
"dev": true
},
"axe-puppeteer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/axe-puppeteer/-/axe-puppeteer-1.1.0.tgz",
"integrity": "sha512-VS17Y1rDQe6A0PdeTPxwOSBfmOdj6efgxyre9cN1du1snnVilczSDtQsgifBKBlzoL/3DKfGpgIi+N+zrzODOg==",
"dev": true,
"requires": {
"axe-core": "^3.5.3"
}
},
"babel-code-frame": { "babel-code-frame": {
"version": "6.26.0", "version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
@@ -9327,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",
@@ -10641,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",
@@ -19739,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": {
@@ -20322,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",
@@ -21272,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",
@@ -21323,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",
@@ -21662,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",
@@ -22561,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"
}
} }
} }
}, },
@@ -25461,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": {
@@ -25818,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",
@@ -26886,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",
@@ -26997,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",
@@ -27209,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",
@@ -27516,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",
@@ -116,6 +119,7 @@
"@typescript-eslint/eslint-plugin": "3.2.0", "@typescript-eslint/eslint-plugin": "3.2.0",
"@typescript-eslint/parser": "3.2.0", "@typescript-eslint/parser": "3.2.0",
"adal-angular": "1.0.15", "adal-angular": "1.0.15",
"axe-puppeteer": "1.1.0",
"babel-jest": "24.9.0", "babel-jest": "24.9.0",
"babel-loader": "8.1.0", "babel-loader": "8.1.0",
"buffer": "5.1.0", "buffer": "5.1.0",
@@ -146,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",
@@ -186,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 {
@@ -553,6 +551,7 @@ export interface GraphParameters extends RpParameters {
pk: string; pk: string;
coll: string; coll: string;
cd: Boolean; cd: Boolean;
indexingPolicy?: IndexingPolicy;
} }
export interface CreationRequest { export interface CreationRequest {
@@ -570,6 +569,7 @@ export interface SqlCollectionParameters extends RpParameters {
coll: string; coll: string;
cd: Boolean; cd: Boolean;
analyticalStorageTtl?: number; analyticalStorageTtl?: number;
indexingPolicy?: IndexingPolicy;
} }
export interface MongoCreationRequest extends CreationRequest { export interface MongoCreationRequest extends CreationRequest {
@@ -588,6 +588,7 @@ export interface GraphCreationRequest extends CreationRequest {
resource: { resource: {
id: string; id: string;
partitionKey: {}; partitionKey: {};
indexingPolicy?: IndexingPolicy;
}; };
options: RpOptions; options: RpOptions;
}; };
@@ -613,6 +614,7 @@ export interface SqlCollectionCreationRequest extends CreationRequest {
id: string; id: string;
partitionKey: {}; partitionKey: {};
analyticalStorageTtl?: number; analyticalStorageTtl?: number;
indexingPolicy?: IndexingPolicy;
}; };
options: RpOptions; options: RpOptions;
}; };

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

@@ -3,6 +3,7 @@ import { Dialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button"; import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField"; import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
import { Link } from "office-ui-fabric-react/lib/Link"; import { Link } from "office-ui-fabric-react/lib/Link";
import { FontIcon } from "office-ui-fabric-react";
export interface TextFieldProps extends ITextFieldProps { export interface TextFieldProps extends ITextFieldProps {
label: string; label: string;
@@ -84,7 +85,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
{textFieldProps && <TextField {...textFieldProps} />} {textFieldProps && <TextField {...textFieldProps} />}
{linkProps && ( {linkProps && (
<Link href={linkProps.linkUrl} target="_blank"> <Link href={linkProps.linkUrl} target="_blank">
{linkProps.linkText} {linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
</Link> </Link>
)} )}
<DialogFooter> <DialogFooter>

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

@@ -5,11 +5,9 @@ export class GalleryHeaderComponent extends React.Component {
private static readonly azureText = "Microsoft Azure"; private static readonly azureText = "Microsoft Azure";
private static readonly cosmosdbText = "Cosmos DB"; private static readonly cosmosdbText = "Cosmos DB";
private static readonly galleryText = "Gallery"; private static readonly galleryText = "Gallery";
private static readonly loginText = "Log in"; private static readonly loginText = "Sign In";
private static readonly openPortal = () => window.open("https://portal.azure.com", "_blank"); private static readonly openPortal = () => window.open("https://portal.azure.com", "_blank");
private static readonly openDataExplorer = () => (window.location.href = new URL("./", window.location.href).href); private static readonly openDataExplorer = () => (window.location.href = new URL("./", window.location.href).href);
private static readonly openGallery = () =>
(window.location.href = new URL("./galleryViewer.html", window.location.href).href);
private static readonly headerItemStyle: React.CSSProperties = { private static readonly headerItemStyle: React.CSSProperties = {
color: "white" color: "white"
}; };
@@ -63,7 +61,7 @@ export class GalleryHeaderComponent extends React.Component {
<Stack.Item> <Stack.Item>
{this.renderHeaderItem( {this.renderHeaderItem(
GalleryHeaderComponent.galleryText, GalleryHeaderComponent.galleryText,
GalleryHeaderComponent.openGallery, undefined,
GalleryHeaderComponent.headerItemTextProps GalleryHeaderComponent.headerItemTextProps
)} )}
</Stack.Item> </Stack.Item>

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

@@ -20,6 +20,7 @@ describe("GalleryCardComponent", () => {
views: 0 views: 0
}, },
isFavorite: false, isFavorite: false,
showDownload: true,
showDelete: true, showDelete: true,
onClick: undefined, onClick: undefined,
onTagClick: undefined, onTagClick: undefined,

View File

@@ -1,4 +1,4 @@
import { Card, ICardTokens } from "@uifabric/react-cards"; import { Card } from "@uifabric/react-cards";
import { import {
FontWeights, FontWeights,
Icon, Icon,
@@ -18,10 +18,12 @@ import * as React from "react";
import { IGalleryItem } from "../../../../Juno/JunoClient"; import { IGalleryItem } from "../../../../Juno/JunoClient";
import { FileSystemUtil } from "../../../Notebook/FileSystemUtil"; import { FileSystemUtil } from "../../../Notebook/FileSystemUtil";
import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg"; import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg";
import { StyleConstants } from "../../../../Common/Constants";
export interface GalleryCardComponentProps { export interface GalleryCardComponentProps {
data: IGalleryItem; data: IGalleryItem;
isFavorite: boolean; isFavorite: boolean;
showDownload: boolean;
showDelete: boolean; showDelete: boolean;
onClick: () => void; onClick: () => void;
onTagClick: (tag: string) => void; onTagClick: (tag: string) => void;
@@ -32,30 +34,30 @@ export interface GalleryCardComponentProps {
} }
export class GalleryCardComponent extends React.Component<GalleryCardComponentProps> { export class GalleryCardComponent extends React.Component<GalleryCardComponentProps> {
public static readonly CARD_HEIGHT = 384;
public static readonly CARD_WIDTH = 256; public static readonly CARD_WIDTH = 256;
private static readonly cardImageHeight = 144; private static readonly cardImageHeight = 144;
private static readonly cardDescriptionMaxChars = 88; private static readonly cardDescriptionMaxChars = 88;
private static readonly cardTokens: ICardTokens = { private static readonly cardItemGapBig = 10;
width: GalleryCardComponent.CARD_WIDTH, private static readonly cardItemGapSmall = 8;
height: GalleryCardComponent.CARD_HEIGHT,
childrenGap: 8,
childrenMargin: 10
};
public render(): JSX.Element { public render(): JSX.Element {
const cardButtonsVisible = this.props.isFavorite !== undefined || this.props.showDownload || this.props.showDelete;
const options: Intl.DateTimeFormatOptions = { const options: Intl.DateTimeFormatOptions = {
year: "numeric", year: "numeric",
month: "short", month: "short",
day: "numeric" day: "numeric"
}; };
const dateString = new Date(this.props.data.created).toLocaleString("default", options); const dateString = new Date(this.props.data.created).toLocaleString("default", options);
const cardTitle = FileSystemUtil.stripExtension(this.props.data.name, "ipynb");
return ( return (
<Card aria-label="Notebook Card" tokens={GalleryCardComponent.cardTokens} onClick={this.props.onClick}> <Card
<Card.Item> aria-label={cardTitle}
data-is-focusable="true"
tokens={{ width: GalleryCardComponent.CARD_WIDTH, childrenGap: 0 }}
onClick={event => this.onClick(event, this.props.onClick)}
>
<Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}>
<Persona <Persona
imageUrl={this.props.data.isSample && CosmosDBLogo} imageUrl={this.props.data.isSample && CosmosDBLogo}
text={this.props.data.author} text={this.props.data.author}
@@ -63,69 +65,89 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
/> />
</Card.Item> </Card.Item>
<Card.Item fill> <Card.Item>
<Image <Image
src={ src={this.props.data.thumbnailUrl}
this.props.data.thumbnailUrl ||
`https://placehold.it/${GalleryCardComponent.CARD_WIDTH}x${GalleryCardComponent.cardImageHeight}`
}
width={GalleryCardComponent.CARD_WIDTH} width={GalleryCardComponent.CARD_WIDTH}
height={GalleryCardComponent.cardImageHeight} height={GalleryCardComponent.cardImageHeight}
imageFit={ImageFit.cover} imageFit={ImageFit.cover}
alt="Notebook cover image" alt={`${cardTitle} cover image`}
/> />
</Card.Item> </Card.Item>
<Card.Section> <Card.Section styles={{ root: { padding: GalleryCardComponent.cardItemGapBig } }}>
<Text variant="small" nowrap> <Text variant="small" nowrap>
{this.props.data.tags?.map((tag, index, array) => ( {this.props.data.tags?.map((tag, index, array) => (
<span key={tag}> <span key={tag}>
<Link onClick={(event): void => this.onTagClick(event, tag)}>{tag}</Link> <Link onClick={event => this.onClick(event, () => this.props.onTagClick(tag))}>{tag}</Link>
{index === array.length - 1 ? <></> : ", "} {index === array.length - 1 ? <></> : ", "}
</span> </span>
))} ))}
</Text> </Text>
<Text styles={{ root: { fontWeight: FontWeights.semibold } }} nowrap>
{FileSystemUtil.stripExtension(this.props.data.name, "ipynb")} <Text
styles={{
root: {
fontWeight: FontWeights.semibold,
paddingTop: GalleryCardComponent.cardItemGapSmall,
paddingBottom: GalleryCardComponent.cardItemGapSmall
}
}}
nowrap
>
{cardTitle}
</Text> </Text>
<Text variant="small" styles={{ root: { height: 36 } }}> <Text variant="small" styles={{ root: { height: 36 } }}>
{this.props.data.description.substr(0, GalleryCardComponent.cardDescriptionMaxChars)} {this.props.data.description.substr(0, GalleryCardComponent.cardDescriptionMaxChars)}
</Text> </Text>
<span>
{this.generateIconText("RedEye", this.props.data.views.toString())}
{this.generateIconText("Download", this.props.data.downloads.toString())}
{this.props.isFavorite !== undefined &&
this.generateIconText("Heart", this.props.data.favorites.toString())}
</span>
</Card.Section> </Card.Section>
<Card.Section horizontal styles={{ root: { alignItems: "flex-end" } }}> {cardButtonsVisible && (
{this.generateIconText("RedEye", this.props.data.views.toString())} <Card.Section
{this.generateIconText("Download", this.props.data.downloads.toString())} styles={{
{this.props.isFavorite !== undefined && this.generateIconText("Heart", this.props.data.favorites.toString())} root: {
</Card.Section> marginLeft: GalleryCardComponent.cardItemGapBig,
marginRight: GalleryCardComponent.cardItemGapBig
}
}}
>
<Separator styles={{ root: { padding: 0, height: 1 } }} />
<Card.Item> <span>
<Separator styles={{ root: { padding: 0, height: 1 } }} /> {this.props.isFavorite !== undefined &&
</Card.Item> this.generateIconButtonWithTooltip(
this.props.isFavorite ? "HeartFill" : "Heart",
this.props.isFavorite ? "Unlike" : "Like",
"left",
this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick
)}
<Card.Section horizontal styles={{ root: { marginTop: 0 } }}> {this.props.showDownload &&
{this.props.isFavorite !== undefined && this.generateIconButtonWithTooltip("Download", "Download", "left", this.props.onDownloadClick)}
this.generateIconButtonWithTooltip(
this.props.isFavorite ? "HeartFill" : "Heart",
this.props.isFavorite ? "Unlike" : "Like",
this.props.isFavorite ? this.onUnfavoriteClick : this.onFavoriteClick
)}
{this.generateIconButtonWithTooltip("Download", "Download", this.onDownloadClick)} {this.props.showDelete &&
this.generateIconButtonWithTooltip("Delete", "Remove", "right", this.props.onDeleteClick)}
{this.props.showDelete && ( </span>
<div style={{ width: "100%", textAlign: "right" }}> </Card.Section>
{this.generateIconButtonWithTooltip("Delete", "Remove", this.onDeleteClick)} )}
</div>
)}
</Card.Section>
</Card> </Card>
); );
} }
private generateIconText = (iconName: string, text: string): JSX.Element => { private generateIconText = (iconName: string, text: string): JSX.Element => {
return ( return (
<Text variant="tiny" styles={{ root: { color: "#ccc" } }}> <Text
variant="tiny"
styles={{ root: { color: StyleConstants.BaseMedium, paddingRight: GalleryCardComponent.cardItemGapSmall } }}
>
<Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} /> {text} <Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} /> {text}
</Text> </Text>
); );
@@ -138,70 +160,37 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
private generateIconButtonWithTooltip = ( private generateIconButtonWithTooltip = (
iconName: string, iconName: string,
title: string, title: string,
onClick: ( horizontalAlign: "right" | "left",
event: React.MouseEvent< activate: () => void
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>
) => void
): JSX.Element => { ): JSX.Element => {
return ( return (
<TooltipHost <TooltipHost
content={title} content={title}
id={`TooltipHost-IconButton-${iconName}`} id={`TooltipHost-IconButton-${iconName}`}
calloutProps={{ gapSpace: 0 }} calloutProps={{ gapSpace: 0 }}
styles={{ root: { display: "inline-block" } }} styles={{ root: { display: "inline-block", float: horizontalAlign } }}
> >
<IconButton iconProps={{ iconName }} title={title} ariaLabel={title} onClick={onClick} /> <IconButton
iconProps={{ iconName }}
title={title}
ariaLabel={title}
onClick={event => this.onClick(event, activate)}
/>
</TooltipHost> </TooltipHost>
); );
}; };
private onTagClick = ( private onClick = (
event: React.MouseEvent<HTMLElement | HTMLAnchorElement | HTMLButtonElement | LinkBase, MouseEvent>, event:
tag: string | React.MouseEvent<HTMLElement | HTMLAnchorElement | HTMLButtonElement | LinkBase, MouseEvent>
| React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>,
activate: () => void
): void => { ): void => {
event.stopPropagation(); event.stopPropagation();
this.props.onTagClick(tag); event.preventDefault();
}; activate();
private onFavoriteClick = (
event: React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>
): void => {
event.stopPropagation();
this.props.onFavoriteClick();
};
private onUnfavoriteClick = (
event: React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>
): void => {
event.stopPropagation();
this.props.onUnfavoriteClick();
};
private onDownloadClick = (
event: React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>
): void => {
event.stopPropagation();
this.props.onDownloadClick();
};
private onDeleteClick = (
event: React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>
): void => {
event.stopPropagation();
this.props.onDeleteClick();
}; };
} }

View File

@@ -2,35 +2,47 @@
exports[`GalleryCardComponent renders 1`] = ` exports[`GalleryCardComponent renders 1`] = `
<Card <Card
aria-label="Notebook Card" aria-label="name"
data-is-focusable="true"
onClick={[Function]}
tokens={ tokens={
Object { Object {
"childrenGap": 8, "childrenGap": 0,
"childrenMargin": 10,
"height": 384,
"width": 256, "width": 256,
} }
} }
> >
<CardItem> <CardItem
tokens={
Object {
"padding": 10,
}
}
>
<StyledPersonaBase <StyledPersonaBase
imageUrl={false} imageUrl={false}
secondaryText="Invalid Date" secondaryText="Invalid Date"
text="author" text="author"
/> />
</CardItem> </CardItem>
<CardItem <CardItem>
fill={true}
>
<Memo(StyledImageBase) <Memo(StyledImageBase)
alt="Notebook cover image" alt="name cover image"
height={144} height={144}
imageFit={2} imageFit={2}
src="thumbnailUrl" src="thumbnailUrl"
width={256} width={256}
/> />
</CardItem> </CardItem>
<CardSection> <CardSection
styles={
Object {
"root": Object {
"padding": 10,
},
}
}
>
<Text <Text
nowrap={true} nowrap={true}
variant="small" variant="small"
@@ -51,6 +63,8 @@ exports[`GalleryCardComponent renders 1`] = `
Object { Object {
"root": Object { "root": Object {
"fontWeight": 600, "fontWeight": 600,
"paddingBottom": 8,
"paddingTop": 8,
}, },
} }
} }
@@ -69,88 +83,91 @@ exports[`GalleryCardComponent renders 1`] = `
> >
description description
</Text> </Text>
<span>
<Text
styles={
Object {
"root": Object {
"color": undefined,
"paddingRight": 8,
},
}
}
variant="tiny"
>
<Memo(StyledIconBase)
iconName="RedEye"
styles={
Object {
"root": Object {
"verticalAlign": "middle",
},
}
}
/>
0
</Text>
<Text
styles={
Object {
"root": Object {
"color": undefined,
"paddingRight": 8,
},
}
}
variant="tiny"
>
<Memo(StyledIconBase)
iconName="Download"
styles={
Object {
"root": Object {
"verticalAlign": "middle",
},
}
}
/>
0
</Text>
<Text
styles={
Object {
"root": Object {
"color": undefined,
"paddingRight": 8,
},
}
}
variant="tiny"
>
<Memo(StyledIconBase)
iconName="Heart"
styles={
Object {
"root": Object {
"verticalAlign": "middle",
},
}
}
/>
0
</Text>
</span>
</CardSection> </CardSection>
<CardSection <CardSection
horizontal={true}
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"alignItems": "flex-end", "marginLeft": 10,
"marginRight": 10,
}, },
} }
} }
> >
<Text
styles={
Object {
"root": Object {
"color": "#ccc",
},
}
}
variant="tiny"
>
<Memo(StyledIconBase)
iconName="RedEye"
styles={
Object {
"root": Object {
"verticalAlign": "middle",
},
}
}
/>
0
</Text>
<Text
styles={
Object {
"root": Object {
"color": "#ccc",
},
}
}
variant="tiny"
>
<Memo(StyledIconBase)
iconName="Download"
styles={
Object {
"root": Object {
"verticalAlign": "middle",
},
}
}
/>
0
</Text>
<Text
styles={
Object {
"root": Object {
"color": "#ccc",
},
}
}
variant="tiny"
>
<Memo(StyledIconBase)
iconName="Heart"
styles={
Object {
"root": Object {
"verticalAlign": "middle",
},
}
}
/>
0
</Text>
</CardSection>
<CardItem>
<Styled <Styled
styles={ styles={
Object { Object {
@@ -161,79 +178,63 @@ exports[`GalleryCardComponent renders 1`] = `
} }
} }
/> />
</CardItem> <span>
<CardSection <StyledTooltipHostBase
horizontal={true} calloutProps={
styles={
Object {
"root": Object {
"marginTop": 0,
},
}
}
>
<StyledTooltipHostBase
calloutProps={
Object {
"gapSpace": 0,
}
}
content="Like"
id="TooltipHost-IconButton-Heart"
styles={
Object {
"root": Object {
"display": "inline-block",
},
}
}
>
<CustomizedIconButton
ariaLabel="Like"
iconProps={
Object { Object {
"iconName": "Heart", "gapSpace": 0,
} }
} }
onClick={[Function]} content="Like"
title="Like" id="TooltipHost-IconButton-Heart"
/> styles={
</StyledTooltipHostBase>
<StyledTooltipHostBase
calloutProps={
Object {
"gapSpace": 0,
}
}
content="Download"
id="TooltipHost-IconButton-Download"
styles={
Object {
"root": Object {
"display": "inline-block",
},
}
}
>
<CustomizedIconButton
ariaLabel="Download"
iconProps={
Object { Object {
"iconName": "Download", "root": Object {
"display": "inline-block",
"float": "left",
},
} }
} }
onClick={[Function]} >
title="Download" <CustomizedIconButton
/> ariaLabel="Like"
</StyledTooltipHostBase> iconProps={
<div Object {
style={ "iconName": "Heart",
Object { }
"textAlign": "right", }
"width": "100%", onClick={[Function]}
title="Like"
/>
</StyledTooltipHostBase>
<StyledTooltipHostBase
calloutProps={
Object {
"gapSpace": 0,
}
} }
} content="Download"
> id="TooltipHost-IconButton-Download"
styles={
Object {
"root": Object {
"display": "inline-block",
"float": "left",
},
}
}
>
<CustomizedIconButton
ariaLabel="Download"
iconProps={
Object {
"iconName": "Download",
}
}
onClick={[Function]}
title="Download"
/>
</StyledTooltipHostBase>
<StyledTooltipHostBase <StyledTooltipHostBase
calloutProps={ calloutProps={
Object { Object {
@@ -246,6 +247,7 @@ exports[`GalleryCardComponent renders 1`] = `
Object { Object {
"root": Object { "root": Object {
"display": "inline-block", "display": "inline-block",
"float": "right",
}, },
} }
} }
@@ -261,7 +263,7 @@ exports[`GalleryCardComponent renders 1`] = `
title="Remove" title="Remove"
/> />
</StyledTooltipHostBase> </StyledTooltipHostBase>
</div> </span>
</CardSection> </CardSection>
</Card> </Card>
`; `;

View File

@@ -0,0 +1,114 @@
import * as React from "react";
import { JunoClient, IGalleryItem } from "../../../Juno/JunoClient";
import { GalleryTab, SortBy, GalleryViewerComponentProps, GalleryViewerComponent } from "./GalleryViewerComponent";
import { NotebookViewerComponentProps, NotebookViewerComponent } from "../NotebookViewer/NotebookViewerComponent";
import * as GalleryUtils from "../../../Utils/GalleryUtils";
import Explorer from "../../Explorer";
export interface GalleryAndNotebookViewerComponentProps {
container?: Explorer;
junoClient: JunoClient;
notebookUrl?: string;
galleryItem?: IGalleryItem;
isFavorite?: boolean;
selectedTab: GalleryTab;
sortBy: SortBy;
searchText: string;
}
interface GalleryAndNotebookViewerComponentState {
notebookUrl: string;
galleryItem: IGalleryItem;
isFavorite: boolean;
selectedTab: GalleryTab;
sortBy: SortBy;
searchText: string;
}
export class GalleryAndNotebookViewerComponent extends React.Component<
GalleryAndNotebookViewerComponentProps,
GalleryAndNotebookViewerComponentState
> {
constructor(props: GalleryAndNotebookViewerComponentProps) {
super(props);
this.state = {
notebookUrl: props.notebookUrl,
galleryItem: props.galleryItem,
isFavorite: props.isFavorite,
selectedTab: props.selectedTab,
sortBy: props.sortBy,
searchText: props.searchText
};
}
public render(): JSX.Element {
if (this.state.notebookUrl) {
const props: NotebookViewerComponentProps = {
container: this.props.container,
junoClient: this.props.junoClient,
notebookUrl: this.state.notebookUrl,
galleryItem: this.state.galleryItem,
isFavorite: this.state.isFavorite,
backNavigationText: GalleryUtils.getTabTitle(this.state.selectedTab),
onBackClick: this.onBackClick,
onTagClick: this.loadTaggedItems
};
return <NotebookViewerComponent {...props} />;
}
const props: GalleryViewerComponentProps = {
container: this.props.container,
junoClient: this.props.junoClient,
selectedTab: this.state.selectedTab,
sortBy: this.state.sortBy,
searchText: this.state.searchText,
openNotebook: this.openNotebook,
onSelectedTabChange: this.onSelectedTabChange,
onSortByChange: this.onSortByChange,
onSearchTextChange: this.onSearchTextChange
};
return <GalleryViewerComponent {...props} />;
}
private onBackClick = (): void => {
this.setState({
notebookUrl: undefined
});
};
private loadTaggedItems = (tag: string): void => {
this.setState({
notebookUrl: undefined,
searchText: tag
});
};
private openNotebook = (data: IGalleryItem, isFavorite: boolean): void => {
this.setState({
notebookUrl: this.props.junoClient.getNotebookContentUrl(data.id),
galleryItem: data,
isFavorite
});
};
private onSelectedTabChange = (selectedTab: GalleryTab): void => {
this.setState({
selectedTab
});
};
private onSortByChange = (sortBy: SortBy): void => {
this.setState({
sortBy
});
};
private onSearchTextChange = (searchText: string): void => {
this.setState({
searchText
});
};
}

View File

@@ -0,0 +1,23 @@
import ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import {
GalleryAndNotebookViewerComponentProps,
GalleryAndNotebookViewerComponent
} from "./GalleryAndNotebookViewerComponent";
export class GalleryAndNotebookViewerComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(private props: GalleryAndNotebookViewerComponentProps) {
this.parameters = ko.observable<number>(Date.now());
}
public renderComponent(): JSX.Element {
return <GalleryAndNotebookViewerComponent {...this.props} />;
}
public triggerRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}

View File

@@ -9,6 +9,7 @@ describe("GalleryViewerComponent", () => {
selectedTab: GalleryTab.OfficialSamples, selectedTab: GalleryTab.OfficialSamples,
sortBy: SortBy.MostViewed, sortBy: SortBy.MostViewed,
searchText: undefined, searchText: undefined,
openNotebook: undefined,
onSelectedTabChange: undefined, onSelectedTabChange: undefined,
onSortByChange: undefined, onSortByChange: undefined,
onSearchTextChange: undefined onSearchTextChange: undefined

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,13 +23,15 @@ 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;
searchText: string; searchText: string;
openNotebook: (data: IGalleryItem, isFavorite: boolean) => void;
onSelectedTabChange: (newTab: GalleryTab) => void; onSelectedTabChange: (newTab: GalleryTab) => void;
onSortByChange: (sortBy: SortBy) => void; onSortByChange: (sortBy: SortBy) => void;
onSearchTextChange: (searchText: string) => void; onSearchTextChange: (searchText: string) => void;
@@ -66,13 +67,14 @@ interface GalleryTabInfo {
content: JSX.Element; content: JSX.Element;
} }
export class GalleryViewerComponent extends React.Component<GalleryViewerComponentProps, GalleryViewerComponentState> export class GalleryViewerComponent extends React.Component<GalleryViewerComponentProps, GalleryViewerComponentState> {
implements GalleryUtils.DialogEnabledComponent {
public static readonly OfficialSamplesTitle = "Official samples"; public static readonly OfficialSamplesTitle = "Official samples";
public static readonly PublicGalleryTitle = "Public gallery"; public static readonly PublicGalleryTitle = "Public gallery";
public static readonly FavoritesTitle = "Liked"; public static readonly FavoritesTitle = "Liked";
public static readonly PublishedTitle = "Your published work"; public static readonly PublishedTitle = "Your published work";
private static readonly rowsPerPage = 5;
private static readonly mostViewedText = "Most viewed"; private static readonly mostViewedText = "Most viewed";
private static readonly mostDownloadedText = "Most downloaded"; private static readonly mostDownloadedText = "Most downloaded";
private static readonly mostFavoritedText = "Most liked"; private static readonly mostFavoritedText = "Most liked";
@@ -128,10 +130,6 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
} }
} }
setDialogProps = (dialogProps: DialogProps): void => {
this.setState({ dialogProps });
};
public render(): JSX.Element { public render(): JSX.Element {
const tabs: GalleryTabInfo[] = [this.createTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)]; const tabs: GalleryTabInfo[] = [this.createTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
@@ -178,8 +176,8 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private createTabContent(data: IGalleryItem[]): JSX.Element { private createTabContent(data: IGalleryItem[]): JSX.Element {
return ( return (
<Stack tokens={{ childrenGap: 20 }}> <Stack tokens={{ childrenGap: 10 }}>
<Stack horizontal tokens={{ childrenGap: 20 }}> <Stack horizontal tokens={{ childrenGap: 20, padding: 10 }}>
<Stack.Item grow> <Stack.Item grow>
<SearchBox value={this.state.searchText} placeholder="Search" onChange={this.onSearchBoxChange} /> <SearchBox value={this.state.searchText} placeholder="Search" onChange={this.onSearchBoxChange} />
</Stack.Item> </Stack.Item>
@@ -389,8 +387,10 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
} }
private getPageSpecification = (itemIndex?: number, visibleRect?: IRectangle): IPageSpecification => { private getPageSpecification = (itemIndex?: number, visibleRect?: IRectangle): IPageSpecification => {
this.columnCount = Math.floor(visibleRect.width / GalleryCardComponent.CARD_WIDTH); if (itemIndex === 0) {
this.rowCount = Math.floor(visibleRect.height / GalleryCardComponent.CARD_HEIGHT); this.columnCount = Math.floor(visibleRect.width / GalleryCardComponent.CARD_WIDTH) || this.columnCount;
this.rowCount = GalleryViewerComponent.rowsPerPage;
}
return { return {
height: visibleRect.height, height: visibleRect.height,
@@ -406,8 +406,9 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
const props: GalleryCardComponentProps = { const props: GalleryCardComponentProps = {
data, data,
isFavorite, isFavorite,
showDownload: !!this.props.container,
showDelete: this.state.selectedTab === GalleryTab.Published, showDelete: this.state.selectedTab === GalleryTab.Published,
onClick: () => this.openNotebook(data, isFavorite), onClick: () => this.props.openNotebook(data, isFavorite),
onTagClick: this.loadTaggedItems, onTagClick: this.loadTaggedItems,
onFavoriteClick: () => this.favoriteItem(data), onFavoriteClick: () => this.favoriteItem(data),
onUnfavoriteClick: () => this.unfavoriteItem(data), onUnfavoriteClick: () => this.unfavoriteItem(data),
@@ -422,20 +423,6 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
); );
}; };
private openNotebook = (data: IGalleryItem, isFavorite: boolean): void => {
if (this.props.container && this.props.junoClient) {
this.props.container.openGallery(this.props.junoClient.getNotebookContentUrl(data.id), data, isFavorite);
} else {
const params = new URLSearchParams({
[GalleryUtils.NotebookViewerParams.NotebookUrl]: this.props.junoClient.getNotebookContentUrl(data.id),
[GalleryUtils.NotebookViewerParams.GalleryItemId]: data.id
});
const location = new URL("./notebookViewer.html", window.location.href).href;
window.open(`${location}?${params.toString()}`);
}
};
private loadTaggedItems = (tag: string): void => { private loadTaggedItems = (tag: string): void => {
const searchText = tag; const searchText = tag;
this.setState({ this.setState({
@@ -465,9 +452,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
}; };
private downloadItem = async (data: IGalleryItem): Promise<void> => { private downloadItem = async (data: IGalleryItem): Promise<void> => {
GalleryUtils.downloadItem(this, this.props.container, this.props.junoClient, data, item => GalleryUtils.downloadItem(this.props.container, this.props.junoClient, data, item => this.refreshSelectedTab(item));
this.refreshSelectedTab(item)
);
}; };
private deleteItem = async (data: IGalleryItem): Promise<void> => { private deleteItem = async (data: IGalleryItem): Promise<void> => {

View File

@@ -21,7 +21,7 @@ exports[`GalleryViewerComponent renders 1`] = `
<Stack <Stack
tokens={ tokens={
Object { Object {
"childrenGap": 20, "childrenGap": 10,
} }
} }
> >
@@ -30,6 +30,7 @@ exports[`GalleryViewerComponent renders 1`] = `
tokens={ tokens={
Object { Object {
"childrenGap": 20, "childrenGap": 20,
"padding": 10,
} }
} }
> >

View File

@@ -16,11 +16,12 @@ import * as React from "react";
import { IGalleryItem } from "../../../Juno/JunoClient"; import { IGalleryItem } from "../../../Juno/JunoClient";
import { FileSystemUtil } from "../../Notebook/FileSystemUtil"; import { FileSystemUtil } from "../../Notebook/FileSystemUtil";
import "./NotebookViewerComponent.less"; import "./NotebookViewerComponent.less";
import CosmosDBLogo from "../../../../images/CosmosDB-logo.svg";
export interface NotebookMetadataComponentProps { export interface NotebookMetadataComponentProps {
data: IGalleryItem; data: IGalleryItem;
isFavorite: boolean; isFavorite: boolean;
downloadButtonText: string; downloadButtonText?: string;
onTagClick: (tag: string) => void; onTagClick: (tag: string) => void;
onFavoriteClick: () => void; onFavoriteClick: () => void;
onUnfavoriteClick: () => void; onUnfavoriteClick: () => void;
@@ -54,11 +55,18 @@ export class NotebookMetadataComponent extends React.Component<NotebookMetadataC
</> </>
)} )}
</Text> </Text>
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
{this.props.downloadButtonText && (
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
)}
</Stack> </Stack>
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 10 }}> <Stack horizontal verticalAlign="center" tokens={{ childrenGap: 10 }}>
<Persona text={this.props.data.author} size={PersonaSize.size32} /> <Persona
imageUrl={this.props.data.isSample && CosmosDBLogo}
text={this.props.data.author}
size={PersonaSize.size32}
/>
<Text>{dateString}</Text> <Text>{dateString}</Text>
<Text> <Text>
<Icon iconName="RedEye" /> {this.props.data.views} <Icon iconName="RedEye" /> {this.props.data.views}

View File

@@ -3,7 +3,7 @@
*/ */
import { Notebook } from "@nteract/commutable"; import { Notebook } from "@nteract/commutable";
import { createContentRef } from "@nteract/core"; import { createContentRef } from "@nteract/core";
import { Icon, Link } from "office-ui-fabric-react"; import { Icon, Link, ProgressIndicator } from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import { contents } from "rx-jupyter"; import { contents } from "rx-jupyter";
import * as Logger from "../../../Common/Logger"; import * as Logger from "../../../Common/Logger";
@@ -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;
@@ -36,10 +37,13 @@ interface NotebookViewerComponentState {
galleryItem?: IGalleryItem; galleryItem?: IGalleryItem;
isFavorite?: boolean; isFavorite?: boolean;
dialogProps: DialogProps; dialogProps: DialogProps;
showProgressBar: boolean;
} }
export class NotebookViewerComponent extends React.Component<NotebookViewerComponentProps, NotebookViewerComponentState> export class NotebookViewerComponent extends React.Component<
implements GalleryUtils.DialogEnabledComponent { NotebookViewerComponentProps,
NotebookViewerComponentState
> {
private clientManager: NotebookClientV2; private clientManager: NotebookClientV2;
private notebookComponentBootstrapper: NotebookComponentBootstrapper; private notebookComponentBootstrapper: NotebookComponentBootstrapper;
@@ -65,26 +69,24 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
content: undefined, content: undefined,
galleryItem: props.galleryItem, galleryItem: props.galleryItem,
isFavorite: props.isFavorite, isFavorite: props.isFavorite,
dialogProps: undefined dialogProps: undefined,
showProgressBar: true
}; };
this.loadNotebookContent(); this.loadNotebookContent();
} }
setDialogProps = (dialogProps: DialogProps): void => {
this.setState({ dialogProps });
};
private async loadNotebookContent(): Promise<void> { private async loadNotebookContent(): Promise<void> {
try { try {
const response = await fetch(this.props.notebookUrl); const response = await fetch(this.props.notebookUrl);
if (!response.ok) { if (!response.ok) {
this.setState({ showProgressBar: false });
throw new Error(`Received HTTP ${response.status} while fetching ${this.props.notebookUrl}`); throw new Error(`Received HTTP ${response.status} while fetching ${this.props.notebookUrl}`);
} }
const notebook: Notebook = await response.json(); const notebook: Notebook = await response.json();
this.notebookComponentBootstrapper.setContent("json", notebook); this.notebookComponentBootstrapper.setContent("json", notebook);
this.setState({ content: notebook }); this.setState({ content: notebook, showProgressBar: false });
if (this.props.galleryItem) { if (this.props.galleryItem) {
const response = await this.props.junoClient.increaseNotebookViews(this.props.galleryItem.id); const response = await this.props.junoClient.increaseNotebookViews(this.props.galleryItem.id);
@@ -95,6 +97,7 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
this.setState({ galleryItem: response.data }); this.setState({ galleryItem: response.data });
} }
} catch (error) { } catch (error) {
this.setState({ showProgressBar: false });
const message = `Failed to load notebook content: ${error}`; const message = `Failed to load notebook content: ${error}`;
Logger.logError(message, "NotebookViewerComponent/loadNotebookContent"); Logger.logError(message, "NotebookViewerComponent/loadNotebookContent");
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
@@ -117,9 +120,7 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
<NotebookMetadataComponent <NotebookMetadataComponent
data={this.state.galleryItem} data={this.state.galleryItem}
isFavorite={this.state.isFavorite} isFavorite={this.state.isFavorite}
downloadButtonText={ downloadButtonText={this.props.container && "Download to my notebooks"}
this.props.container ? "Download to my notebooks" : "Edit/Run in Cosmos DB data explorer"
}
onTagClick={this.props.onTagClick} onTagClick={this.props.onTagClick}
onFavoriteClick={this.favoriteItem} onFavoriteClick={this.favoriteItem}
onUnfavoriteClick={this.unfavoriteItem} onUnfavoriteClick={this.unfavoriteItem}
@@ -130,6 +131,8 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
<></> <></>
)} )}
{this.state.showProgressBar && <ProgressIndicator />}
{this.notebookComponentBootstrapper.renderComponent(NotebookReadOnlyRenderer, { {this.notebookComponentBootstrapper.renderComponent(NotebookReadOnlyRenderer, {
hideInputs: this.props.hideInputs hideInputs: this.props.hideInputs
})} })}
@@ -173,7 +176,7 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
}; };
private downloadItem = async (): Promise<void> => { private downloadItem = async (): Promise<void> => {
GalleryUtils.downloadItem(this, this.props.container, this.props.junoClient, this.state.galleryItem, item => GalleryUtils.downloadItem(this.props.container, this.props.junoClient, this.state.galleryItem, item =>
this.setState({ galleryItem: item }) this.setState({ galleryItem: item })
); );
}; };

View File

@@ -48,6 +48,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
verticalAlign="center" verticalAlign="center"
> >
<StyledPersonaBase <StyledPersonaBase
imageUrl={false}
size={11} size={11}
text="author" text="author"
/> />
@@ -147,6 +148,7 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
verticalAlign="center" verticalAlign="center"
> >
<StyledPersonaBase <StyledPersonaBase
imageUrl={false}
size={11} size={11}
text="author" text="author"
/> />

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;
@@ -3101,7 +2933,6 @@ export default class Explorer implements ViewModels.Explorer {
if (galleryTab) { if (galleryTab) {
this.tabsManager.activateTab(galleryTab); this.tabsManager.activateTab(galleryTab);
(galleryTab as any).updateGalleryParams(notebookUrl, galleryItem, isFavorite);
} else { } else {
if (!this.galleryTab) { if (!this.galleryTab) {
this.galleryTab = await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab"); this.galleryTab = await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab");

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>;
@@ -889,6 +889,7 @@ export default class AddCollectionPane extends ContextualPaneBase implements Vie
this.container.armEndpoint(), this.container.armEndpoint(),
databaseId, databaseId,
collectionId, collectionId,
indexingPolicy,
offerThroughput, offerThroughput,
partitionKeyPath, partitionKeyPath,
partitionKey.version, partitionKey.version,
@@ -908,6 +909,7 @@ export default class AddCollectionPane extends ContextualPaneBase implements Vie
databaseId, databaseId,
this._getAnalyticalStorageTtl(), this._getAnalyticalStorageTtl(),
collectionId, collectionId,
indexingPolicy,
offerThroughput, offerThroughput,
partitionKeyPath, partitionKeyPath,
partitionKey.version, partitionKey.version,

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,14 +1,13 @@
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 * as ViewModels from "../../Contracts/ViewModels";
import { JunoClient } from "../../Juno/JunoClient"; import { JunoClient } from "../../Juno/JunoClient";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { FileSystemUtil } from "../Notebook/FileSystemUtil";
import { GenericRightPaneComponent, GenericRightPaneProps } from "./GenericRightPaneComponent"; import { GenericRightPaneComponent, GenericRightPaneProps } from "./GenericRightPaneComponent";
import Explorer from "../Explorer";
import { PublishNotebookPaneComponent, PublishNotebookPaneProps } from "./PublishNotebookPaneComponent";
export class PublishNotebookPaneAdapter implements ReactAdapter { export class PublishNotebookPaneAdapter implements ReactAdapter {
parameters: ko.Observable<number>; parameters: ko.Observable<number>;
@@ -22,9 +21,9 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
private content: string; private content: string;
private description: string; private description: string;
private tags: string; private tags: string;
private thumbnailUrl: string; private imageSrc: string;
constructor(private container: ViewModels.Explorer, private junoClient: JunoClient) { constructor(private container: Explorer, private junoClient: JunoClient) {
this.parameters = ko.observable(Date.now()); this.parameters = ko.observable(Date.now());
this.reset(); this.reset();
this.triggerRender(); this.triggerRender();
@@ -87,7 +86,7 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
this.description, this.description,
this.tags?.split(","), this.tags?.split(","),
this.author, this.author,
this.thumbnailUrl, this.imageSrc,
this.content this.content
); );
if (!response.data) { if (!response.data) {
@@ -112,44 +111,37 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
this.close(); this.close();
} }
private createFormErrorForLargeImageSelection = (formError: string, formErrorDetail: string, area: string): void => {
this.formError = formError;
this.formErrorDetail = formErrorDetail;
const message = `${this.formError}: ${this.formErrorDetail}`;
Logger.logError(message, area);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
this.triggerRender();
};
private clearFormError = (): void => {
this.formError = undefined;
this.formErrorDetail = undefined;
this.triggerRender();
};
private createContent = (): JSX.Element => { private createContent = (): JSX.Element => {
const descriptionPara1 = const publishNotebookPaneProps: PublishNotebookPaneProps = {
"This notebook has your data. Please make sure you delete any sensitive data/output before publishing."; notebookName: this.name,
const descriptionPara2 = `Would you like to publish and share ${FileSystemUtil.stripExtension( notebookDescription: "",
this.name, notebookTags: "",
"ipynb" notebookAuthor: this.author,
)} to the gallery?`; notebookCreatedDate: new Date().toISOString(),
const descriptionProps: ITextFieldProps = { onChangeDescription: (newValue: string) => (this.description = newValue),
label: "Description", onChangeTags: (newValue: string) => (this.tags = newValue),
ariaLabel: "Description", onChangeImageSrc: (newValue: string) => (this.imageSrc = newValue),
multiline: true, onError: this.createFormErrorForLargeImageSelection,
rows: 3, clearFormError: this.clearFormError
required: true,
onChange: (event, newValue) => (this.description = newValue)
};
const tagsProps: ITextFieldProps = {
label: "Tags",
ariaLabel: "Tags",
placeholder: "Optional tag 1, Optional tag 2",
onChange: (event, newValue) => (this.tags = newValue)
};
const thumbnailProps: ITextFieldProps = {
label: "Cover image url",
ariaLabel: "Cover image url",
onChange: (event, newValue) => (this.thumbnailUrl = newValue)
}; };
return ( return <PublishNotebookPaneComponent {...publishNotebookPaneProps} />;
<div className="panelContent">
<Stack className="paneMainContent" tokens={{ childrenGap: 20 }}>
<Text>{descriptionPara1}</Text>
<Text>{descriptionPara2}</Text>
<TextField {...descriptionProps} />
<TextField {...tagsProps} />
<TextField {...thumbnailProps} />
</Stack>
</div>
);
}; };
private reset = (): void => { private reset = (): void => {

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,

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