mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-24 19:31:36 +00:00
Compare commits
54 Commits
force-enab
...
Remove-Exp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ecc3d67b0 | ||
|
|
448566146f | ||
|
|
c6766dd69e | ||
|
|
9d411c57b0 | ||
|
|
ff58eb3724 | ||
|
|
e49bcc524f | ||
|
|
cdd6d32990 | ||
|
|
c1dcd0e90b | ||
|
|
e705c490c9 | ||
|
|
72ce5fc813 | ||
|
|
d5f3230f6f | ||
|
|
a07aff1e8c | ||
|
|
d58fececac | ||
|
|
b6d60dcc7b | ||
|
|
2fd6305944 | ||
|
|
914e969083 | ||
|
|
f2585bba14 | ||
|
|
19cf203606 | ||
|
|
19e39ea62f | ||
|
|
f8510659de | ||
|
|
7265708c15 | ||
|
|
a53c203286 | ||
|
|
e0060b12e5 | ||
|
|
a9fd01f9b4 | ||
|
|
d74da34742 | ||
|
|
02ea26da71 | ||
|
|
a264ea2275 | ||
|
|
649b6a93b4 | ||
|
|
2bccb7885f | ||
|
|
3f8e394952 | ||
|
|
f94f95e788 | ||
|
|
6dba2e4792 | ||
|
|
5d4b193865 | ||
|
|
68789c5069 | ||
|
|
1685b34e2a | ||
|
|
56f430ebd8 | ||
|
|
e8033f0bbc | ||
|
|
d96cecdfe8 | ||
|
|
d90a065e63 | ||
|
|
f449328f26 | ||
|
|
41800f9ee5 | ||
|
|
7bdc31aa67 | ||
|
|
1e6ad113dd | ||
|
|
05932e1d38 | ||
|
|
02e6d8442b | ||
|
|
8cf09acc19 | ||
|
|
5cd4e93c65 | ||
|
|
76e3b7e6f1 | ||
|
|
dc5679ffd3 | ||
|
|
88f5e7485a | ||
|
|
662c03580a | ||
|
|
14fd9054dd | ||
|
|
37e0f50ef2 | ||
|
|
3ab6b2a05d |
@@ -45,7 +45,6 @@ src/Definitions/jquery.d.ts
|
|||||||
src/Definitions/plotly.js-cartesian-dist.d-min.ts
|
src/Definitions/plotly.js-cartesian-dist.d-min.ts
|
||||||
src/Definitions/png.d.ts
|
src/Definitions/png.d.ts
|
||||||
src/Definitions/svg.d.ts
|
src/Definitions/svg.d.ts
|
||||||
src/Definitions/worker.d.ts
|
|
||||||
src/Explorer/ComponentRegisterer.test.ts
|
src/Explorer/ComponentRegisterer.test.ts
|
||||||
src/Explorer/ComponentRegisterer.ts
|
src/Explorer/ComponentRegisterer.ts
|
||||||
src/Explorer/ContextMenuButtonFactory.ts
|
src/Explorer/ContextMenuButtonFactory.ts
|
||||||
@@ -91,8 +90,7 @@ src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.test.ts
|
|||||||
src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.ts
|
src/Explorer/Graph/GraphExplorerComponent/GremlinSimpleClient.ts
|
||||||
src/Explorer/Graph/GraphStyleComponent/GraphStyle.test.ts
|
src/Explorer/Graph/GraphStyleComponent/GraphStyle.test.ts
|
||||||
src/Explorer/Graph/GraphStyleComponent/GraphStyleComponent.ts
|
src/Explorer/Graph/GraphStyleComponent/GraphStyleComponent.ts
|
||||||
src/Explorer/Graph/NewVertexComponent/NewVertex.test.ts
|
|
||||||
src/Explorer/Graph/NewVertexComponent/NewVertexComponent.ts
|
|
||||||
src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts
|
src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.test.ts
|
||||||
src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.ts
|
src/Explorer/Menus/CommandBar/CommandBarComponentButtonFactory.ts
|
||||||
src/Explorer/Menus/ContextMenu.ts
|
src/Explorer/Menus/ContextMenu.ts
|
||||||
@@ -121,21 +119,16 @@ 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/ContextualPaneBase.ts
|
src/Explorer/Panes/ContextualPaneBase.ts
|
||||||
src/Explorer/Panes/DeleteCollectionConfirmationPane.test.ts
|
|
||||||
src/Explorer/Panes/DeleteCollectionConfirmationPane.ts
|
|
||||||
src/Explorer/Panes/DeleteDatabaseConfirmationPane.test.ts
|
src/Explorer/Panes/DeleteDatabaseConfirmationPane.test.ts
|
||||||
src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts
|
src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts
|
||||||
src/Explorer/Panes/GraphStylingPane.ts
|
src/Explorer/Panes/GraphStylingPane.ts
|
||||||
src/Explorer/Panes/NewVertexPane.ts
|
# src/Explorer/Panes/NewVertexPane.ts
|
||||||
src/Explorer/Panes/PaneComponents.ts
|
src/Explorer/Panes/PaneComponents.ts
|
||||||
src/Explorer/Panes/RenewAdHocAccessPane.ts
|
src/Explorer/Panes/RenewAdHocAccessPane.ts
|
||||||
src/Explorer/Panes/SetupNotebooksPane.ts
|
|
||||||
src/Explorer/Panes/StringInputPane.ts
|
src/Explorer/Panes/StringInputPane.ts
|
||||||
src/Explorer/Panes/SwitchDirectoryPane.ts
|
src/Explorer/Panes/SwitchDirectoryPane.ts
|
||||||
src/Explorer/Panes/Tables/AddTableEntityPane.ts
|
|
||||||
src/Explorer/Panes/Tables/EditTableEntityPane.ts
|
src/Explorer/Panes/Tables/EditTableEntityPane.ts
|
||||||
src/Explorer/Panes/Tables/EntityPropertyViewModel.ts
|
src/Explorer/Panes/Tables/EntityPropertyViewModel.ts
|
||||||
src/Explorer/Panes/Tables/QuerySelectPane.ts
|
|
||||||
src/Explorer/Panes/Tables/TableEntityPane.ts
|
src/Explorer/Panes/Tables/TableEntityPane.ts
|
||||||
src/Explorer/Panes/Tables/Validators/EntityPropertyNameValidator.ts
|
src/Explorer/Panes/Tables/Validators/EntityPropertyNameValidator.ts
|
||||||
src/Explorer/Panes/Tables/Validators/EntityPropertyValidationCommon.ts
|
src/Explorer/Panes/Tables/Validators/EntityPropertyValidationCommon.ts
|
||||||
@@ -251,7 +244,6 @@ src/Utils/QueryUtils.test.ts
|
|||||||
src/applyExplorerBindings.ts
|
src/applyExplorerBindings.ts
|
||||||
src/global.d.ts
|
src/global.d.ts
|
||||||
src/setupTests.ts
|
src/setupTests.ts
|
||||||
src/workers/upload/index.ts
|
|
||||||
src/Explorer/Controls/AccessibleElement/AccessibleElement.tsx
|
src/Explorer/Controls/AccessibleElement/AccessibleElement.tsx
|
||||||
src/Explorer/Controls/Accordion/AccordionComponent.tsx
|
src/Explorer/Controls/Accordion/AccordionComponent.tsx
|
||||||
src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.test.tsx
|
src/Explorer/Controls/AccountSwitch/AccountSwitchComponent.test.tsx
|
||||||
@@ -299,8 +291,6 @@ src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.t
|
|||||||
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
|
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.tsx
|
||||||
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
|
src/Explorer/Menus/CommandBar/CommandBarUtil.tsx
|
||||||
src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.test.tsx
|
src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.test.tsx
|
||||||
src/Explorer/Menus/NotificationConsole/NotificationConsoleComponent.tsx
|
|
||||||
src/Explorer/Menus/NotificationConsole/NotificationConsoleComponentAdapter.tsx
|
|
||||||
src/Explorer/Notebook/NotebookComponent/NotebookComponent.tsx
|
src/Explorer/Notebook/NotebookComponent/NotebookComponent.tsx
|
||||||
src/Explorer/Notebook/NotebookComponent/NotebookComponentAdapter.tsx
|
src/Explorer/Notebook/NotebookComponent/NotebookComponentAdapter.tsx
|
||||||
src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx
|
src/Explorer/Notebook/NotebookComponent/NotebookComponentBootstrapper.tsx
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module.exports = {
|
|||||||
browser: true,
|
browser: true,
|
||||||
es6: true,
|
es6: true,
|
||||||
},
|
},
|
||||||
plugins: ["@typescript-eslint", "no-null", "prefer-arrow"],
|
plugins: ["@typescript-eslint", "no-null", "prefer-arrow", "react-hooks"],
|
||||||
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||||
globals: {
|
globals: {
|
||||||
Atomics: "readonly",
|
Atomics: "readonly",
|
||||||
@@ -20,7 +20,7 @@ module.exports = {
|
|||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ["**/*.tsx"],
|
files: ["**/*.tsx"],
|
||||||
extends: ["plugin:react/recommended"], // TODO: Add react-hooks
|
extends: ["plugin:react/recommended"],
|
||||||
plugins: ["react"],
|
plugins: ["react"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -42,6 +42,8 @@ module.exports = {
|
|||||||
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
|
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
|
||||||
eqeqeq: "error",
|
eqeqeq: "error",
|
||||||
"react/display-name": "off",
|
"react/display-name": "off",
|
||||||
|
"react-hooks/rules-of-hooks": "warn", // TODO: error
|
||||||
|
"react-hooks/exhaustive-deps": "warn", // TODO: error
|
||||||
"no-restricted-syntax": [
|
"no-restricted-syntax": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
|||||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[Preview this branch](https://cosmos-explorer-preview.azurewebsites.net/pull/EDIT_THIS_NUMBER_IN_THE_PR_DESCRIPTION?feature.someFeatureFlagYouMightNeed=true)
|
||||||
71
.github/workflows/ci.yml
vendored
71
.github/workflows/ci.yml
vendored
@@ -101,7 +101,8 @@ jobs:
|
|||||||
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
||||||
endtoendemulator:
|
endtoendemulator:
|
||||||
name: "End To End Emulator Tests"
|
name: "End To End Emulator Tests"
|
||||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
# Temporarily disabled. This test needs to be rewritten in playwright
|
||||||
|
if: false
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -126,58 +127,21 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: screenshots
|
name: screenshots
|
||||||
path: failed-*
|
path: failed-*
|
||||||
accessibility:
|
endtoend:
|
||||||
name: "Accessibility | Hosted"
|
name: "E2E"
|
||||||
needs: [lint, format, compile, unittest]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Use Node.js 14.x
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: 14.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
|
|
||||||
endtoendhosted:
|
|
||||||
name: "End to End Tests"
|
|
||||||
needs: [cleanupaccounts]
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||||
PORTAL_RUNNER_SUBSCRIPTION: ${{ secrets.PORTAL_RUNNER_SUBSCRIPTION }}
|
|
||||||
PORTAL_RUNNER_RESOURCE_GROUP: ${{ secrets.PORTAL_RUNNER_RESOURCE_GROUP }}
|
|
||||||
PORTAL_RUNNER_DATABASE_ACCOUNT: ${{ secrets.PORTAL_RUNNER_DATABASE_ACCOUNT }}
|
|
||||||
PORTAL_RUNNER_DATABASE_ACCOUNT_KEY: ${{ secrets.PORTAL_RUNNER_DATABASE_ACCOUNT_KEY }}
|
|
||||||
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT: ${{ secrets.PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT }}
|
|
||||||
PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY: ${{ secrets.PORTAL_RUNNER_MONGO_DATABASE_ACCOUNT_KEY }}
|
|
||||||
NOTEBOOKS_TEST_RUNNER_TENANT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_TENANT_ID }}
|
|
||||||
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }}
|
|
||||||
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
|
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
|
||||||
PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
|
|
||||||
MONGO_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
|
|
||||||
CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }}
|
|
||||||
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
|
|
||||||
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
test-file:
|
test-file:
|
||||||
- ./test/cassandra/container.spec.ts
|
- ./test/cassandra/container.spec.ts
|
||||||
- ./test/mongo/mongoIndexPolicy.spec.ts
|
|
||||||
- ./test/notebooks/uploadAndOpenNotebook.spec.ts
|
|
||||||
- ./test/selfServe/selfServeExample.spec.ts
|
|
||||||
- ./test/sql/container.spec.ts
|
- ./test/sql/container.spec.ts
|
||||||
|
- ./test/mongo/container.spec.ts
|
||||||
|
- ./test/selfServe/selfServeExample.spec.ts
|
||||||
|
- ./test/notebooks/upload.spec.ts
|
||||||
- ./test/sql/resourceToken.spec.ts
|
- ./test/sql/resourceToken.spec.ts
|
||||||
- ./test/tables/container.spec.ts
|
- ./test/tables/container.spec.ts
|
||||||
steps:
|
steps:
|
||||||
@@ -188,30 +152,17 @@ jobs:
|
|||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm start &
|
- run: npm start &
|
||||||
- run: node utils/cleanupDBs.js
|
|
||||||
- run: npm run wait-for-server
|
- run: npm run wait-for-server
|
||||||
- name: ${{ matrix['test-file'] }}
|
- name: ${{ matrix['test-file'] }}
|
||||||
run: npx jest -c ./jest.config.e2e.js --detectOpenHandles ${{ matrix['test-file'] }}
|
run: |
|
||||||
|
# Run tests up to three times
|
||||||
|
for i in $(seq 1 3); do npx jest -c ./jest.config.playwright.js ${{ matrix['test-file'] }} && s=0 && break || s=$? && sleep 1; done; (exit $s)
|
||||||
shell: bash
|
shell: bash
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: screenshots
|
name: screenshots
|
||||||
path: failed-*
|
path: screenshots/
|
||||||
cleanupaccounts:
|
|
||||||
name: "Cleanup Test Database Accounts"
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }}
|
|
||||||
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Use Node.js 14.x
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: 14.x
|
|
||||||
- run: npm ci
|
|
||||||
- run: node utils/cleanupDBs.js
|
|
||||||
nuget:
|
nuget:
|
||||||
name: Publish Nuget
|
name: Publish Nuget
|
||||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||||
|
|||||||
28
.github/workflows/cleanup.yml
vendored
Normal file
28
.github/workflows/cleanup.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# This is a basic workflow to help you get started with Actions
|
||||||
|
|
||||||
|
name: Cleanup End to End Account Resources
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Allows you to run this workflow manually from the Actions tab
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
# Once every hour
|
||||||
|
- cron: "0 * * * *"
|
||||||
|
|
||||||
|
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||||
|
jobs:
|
||||||
|
# This workflow contains a single job called "build"
|
||||||
|
cleanupaccounts:
|
||||||
|
name: "Cleanup Test Database Accounts"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }}
|
||||||
|
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Use Node.js 14.x
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 14.x
|
||||||
|
- run: npm ci
|
||||||
|
- run: node utils/cleanupDBs.js
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -14,4 +14,6 @@ Contracts/*
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.cache/
|
.cache/
|
||||||
.env
|
.env
|
||||||
failure.png
|
failure.png
|
||||||
|
screenshots/*
|
||||||
|
GettingStarted-ignore*.ipynb
|
||||||
@@ -153,7 +153,7 @@ Cosmos Explorer has been under constant development for over 5 years. As a resul
|
|||||||
|
|
||||||
✅ DO
|
✅ DO
|
||||||
|
|
||||||
- Use [Puppeteer](https://developers.google.com/web/tools/puppeteer) and [Jest](https://jestjs.io/)
|
- Use [Playwright](https://github.com/microsoft/playwright) and [Jest](https://jestjs.io/)
|
||||||
- Write or modify an existing E2E test that covers the primary use case of any major feature.
|
- Write or modify an existing E2E test that covers the primary use case of any major feature.
|
||||||
- Use caution. Do not try to cover every case. End to End tests can be slow and brittle.
|
- Use caution. Do not try to cover every case. End to End tests can be slow and brittle.
|
||||||
|
|
||||||
|
|||||||
10
externals/iframeResizer.contentWindow.min.js
vendored
Normal file
10
externals/iframeResizer.contentWindow.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
13
jest-playwright.config.js
Normal file
13
jest-playwright.config.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
const isCI = require("is-ci");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
exitOnPageError: false,
|
||||||
|
launchOptions: {
|
||||||
|
headless: isCI,
|
||||||
|
slowMo: 10,
|
||||||
|
timeout: 60000,
|
||||||
|
},
|
||||||
|
contextOptions: {
|
||||||
|
ignoreHTTPSErrors: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
const isCI = require("is-ci");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
launch: {
|
|
||||||
headless: isCI,
|
|
||||||
slowMo: 55,
|
|
||||||
defaultViewport: null,
|
|
||||||
ignoreHTTPSErrors: true,
|
|
||||||
args: ["--disable-web-security"],
|
|
||||||
exitOnPageError: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
preset: "jest-puppeteer",
|
|
||||||
testMatch: ["<rootDir>/test/**/*.spec.[jt]s?(x)"],
|
|
||||||
setupFiles: ["dotenv/config"],
|
|
||||||
};
|
|
||||||
@@ -69,7 +69,6 @@ module.exports = {
|
|||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
"^.*[.](svg|png|gif|less|css)$": "<rootDir>/mockModule",
|
"^.*[.](svg|png|gif|less|css)$": "<rootDir>/mockModule",
|
||||||
"@nteract/stateful-components/(.*)$": "<rootDir>/mockModule",
|
"@nteract/stateful-components/(.*)$": "<rootDir>/mockModule",
|
||||||
"worker-loader": "<rootDir>/mockModule",
|
|
||||||
"office-ui-fabric-react/lib/(.*)$": "office-ui-fabric-react/lib-commonjs/$1", // https://github.com/OfficeDev/office-ui-fabric-react/wiki/Fabric-6-Release-Notes
|
"office-ui-fabric-react/lib/(.*)$": "office-ui-fabric-react/lib-commonjs/$1", // https://github.com/OfficeDev/office-ui-fabric-react/wiki/Fabric-6-Release-Notes
|
||||||
"^dnd-core$": "dnd-core/dist/cjs",
|
"^dnd-core$": "dnd-core/dist/cjs",
|
||||||
"^react-dnd$": "react-dnd/dist/cjs",
|
"^react-dnd$": "react-dnd/dist/cjs",
|
||||||
|
|||||||
7
jest.config.playwright.js
Normal file
7
jest.config.playwright.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
preset: "jest-playwright-preset",
|
||||||
|
testMatch: ["<rootDir>/test/**/*.spec.[jt]s?(x)"],
|
||||||
|
setupFiles: ["dotenv/config"],
|
||||||
|
testEnvironment: "./test/playwrightEnv.js",
|
||||||
|
setupFilesAfterEnv: ["expect-playwright"],
|
||||||
|
};
|
||||||
4919
package-lock.json
generated
4919
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
@@ -5,7 +5,7 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/arm-cosmosdb": "9.1.0",
|
"@azure/arm-cosmosdb": "9.1.0",
|
||||||
"@azure/cosmos": "3.9.0",
|
"@azure/cosmos": "3.10.5",
|
||||||
"@azure/cosmos-language-service": "0.0.5",
|
"@azure/cosmos-language-service": "0.0.5",
|
||||||
"@azure/identity": "1.2.1",
|
"@azure/identity": "1.2.1",
|
||||||
"@azure/ms-rest-nodeauth": "3.0.7",
|
"@azure/ms-rest-nodeauth": "3.0.7",
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"@nteract/iron-icons": "1.0.0",
|
"@nteract/iron-icons": "1.0.0",
|
||||||
"@nteract/jupyter-widgets": "2.0.0",
|
"@nteract/jupyter-widgets": "2.0.0",
|
||||||
"@nteract/logos": "1.0.0",
|
"@nteract/logos": "1.0.0",
|
||||||
"@nteract/markdown": "4.4.0",
|
"@nteract/markdown": "4.6.0",
|
||||||
"@nteract/monaco-editor": "3.2.2",
|
"@nteract/monaco-editor": "3.2.2",
|
||||||
"@nteract/octicons": "2.0.0",
|
"@nteract/octicons": "2.0.0",
|
||||||
"@nteract/outputs": "3.0.9",
|
"@nteract/outputs": "3.0.9",
|
||||||
@@ -65,6 +65,7 @@
|
|||||||
"i18next": "19.8.4",
|
"i18next": "19.8.4",
|
||||||
"i18next-browser-languagedetector": "6.0.1",
|
"i18next-browser-languagedetector": "6.0.1",
|
||||||
"i18next-http-backend": "1.0.23",
|
"i18next-http-backend": "1.0.23",
|
||||||
|
"iframe-resizer-react": "1.1.0",
|
||||||
"immutable": "4.0.0-rc.12",
|
"immutable": "4.0.0-rc.12",
|
||||||
"is-ci": "2.0.0",
|
"is-ci": "2.0.0",
|
||||||
"jquery": "3.5.1",
|
"jquery": "3.5.1",
|
||||||
@@ -78,6 +79,7 @@
|
|||||||
"office-ui-fabric-react": "7.164.2",
|
"office-ui-fabric-react": "7.164.2",
|
||||||
"p-retry": "4.2.0",
|
"p-retry": "4.2.0",
|
||||||
"plotly.js-cartesian-dist-min": "1.52.3",
|
"plotly.js-cartesian-dist-min": "1.52.3",
|
||||||
|
"post-robot": "10.0.42",
|
||||||
"q": "1.5.1",
|
"q": "1.5.1",
|
||||||
"react": "16.13.1",
|
"react": "16.13.1",
|
||||||
"react-animate-height": "2.0.8",
|
"react-animate-height": "2.0.8",
|
||||||
@@ -92,6 +94,7 @@
|
|||||||
"reflect-metadata": "0.1.13",
|
"reflect-metadata": "0.1.13",
|
||||||
"rx-jupyter": "5.5.12",
|
"rx-jupyter": "5.5.12",
|
||||||
"rxjs": "6.6.3",
|
"rxjs": "6.6.3",
|
||||||
|
"sanitize-html": "2.3.3",
|
||||||
"styled-components": "4.3.2",
|
"styled-components": "4.3.2",
|
||||||
"swr": "0.4.0",
|
"swr": "0.4.0",
|
||||||
"terser-webpack-plugin": "3.1.0",
|
"terser-webpack-plugin": "3.1.0",
|
||||||
@@ -110,26 +113,23 @@
|
|||||||
"@types/d3": "5.9.2",
|
"@types/d3": "5.9.2",
|
||||||
"@types/enzyme": "3.10.7",
|
"@types/enzyme": "3.10.7",
|
||||||
"@types/enzyme-adapter-react-16": "1.0.6",
|
"@types/enzyme-adapter-react-16": "1.0.6",
|
||||||
"@types/expect-puppeteer": "4.4.5",
|
|
||||||
"@types/hasher": "0.0.31",
|
"@types/hasher": "0.0.31",
|
||||||
"@types/jest": "26.0.20",
|
"@types/jest": "26.0.20",
|
||||||
"@types/jest-environment-puppeteer": "4.4.1",
|
|
||||||
"@types/memoize-one": "4.1.1",
|
"@types/memoize-one": "4.1.1",
|
||||||
"@types/node": "12.11.1",
|
"@types/node": "12.11.1",
|
||||||
|
"@types/post-robot": "10.0.1",
|
||||||
"@types/promise.prototype.finally": "2.0.3",
|
"@types/promise.prototype.finally": "2.0.3",
|
||||||
"@types/prop-types": "15.5.8",
|
|
||||||
"@types/puppeteer": "5.4.3",
|
|
||||||
"@types/q": "1.5.1",
|
"@types/q": "1.5.1",
|
||||||
"@types/react": "17.0.0",
|
"@types/react": "17.0.3",
|
||||||
"@types/react-dom": "17.0.0",
|
"@types/react-dom": "17.0.3",
|
||||||
"@types/react-notification-system": "0.2.39",
|
"@types/react-notification-system": "0.2.39",
|
||||||
"@types/react-redux": "7.1.7",
|
"@types/react-redux": "7.1.7",
|
||||||
|
"@types/sanitize-html": "1.27.2",
|
||||||
"@types/sinon": "2.3.3",
|
"@types/sinon": "2.3.3",
|
||||||
"@types/styled-components": "5.1.1",
|
"@types/styled-components": "5.1.1",
|
||||||
"@types/underscore": "1.7.36",
|
"@types/underscore": "1.7.36",
|
||||||
"@typescript-eslint/eslint-plugin": "4.0.1",
|
"@typescript-eslint/eslint-plugin": "4.0.1",
|
||||||
"@typescript-eslint/parser": "4.0.1",
|
"@typescript-eslint/parser": "4.0.1",
|
||||||
"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",
|
||||||
@@ -144,16 +144,18 @@
|
|||||||
"eslint-plugin-no-null": "1.0.2",
|
"eslint-plugin-no-null": "1.0.2",
|
||||||
"eslint-plugin-prefer-arrow": "1.2.2",
|
"eslint-plugin-prefer-arrow": "1.2.2",
|
||||||
"eslint-plugin-react-hooks": "4.2.0",
|
"eslint-plugin-react-hooks": "4.2.0",
|
||||||
|
"expect-playwright": "0.3.3",
|
||||||
"expose-loader": "0.7.5",
|
"expose-loader": "0.7.5",
|
||||||
"fast-glob": "3.2.5",
|
"fast-glob": "3.2.5",
|
||||||
"file-loader": "2.0.0",
|
"file-loader": "2.0.0",
|
||||||
"fs-extra": "7.0.0",
|
"fs-extra": "7.0.0",
|
||||||
|
"html-inline-css-webpack-plugin": "1.11.0",
|
||||||
"html-loader": "0.5.5",
|
"html-loader": "0.5.5",
|
||||||
"html-loader-jest": "0.2.1",
|
"html-loader-jest": "0.2.1",
|
||||||
"html-webpack-plugin": "3.2.0",
|
"html-webpack-plugin": "4.5.2",
|
||||||
"jest": "25.5.4",
|
"jest": "25.5.4",
|
||||||
"jest-canvas-mock": "2.1.0",
|
"jest-canvas-mock": "2.1.0",
|
||||||
"jest-puppeteer": "4.4.0",
|
"jest-playwright-preset": "1.5.1",
|
||||||
"jest-trx-results-processor": "0.0.7",
|
"jest-trx-results-processor": "0.0.7",
|
||||||
"less": "3.8.1",
|
"less": "3.8.1",
|
||||||
"less-loader": "4.1.0",
|
"less-loader": "4.1.0",
|
||||||
@@ -161,23 +163,23 @@
|
|||||||
"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.1",
|
"node-fetch": "2.6.1",
|
||||||
|
"playwright": "1.10.0",
|
||||||
"prettier": "2.2.1",
|
"prettier": "2.2.1",
|
||||||
"puppeteer": "8.0.0",
|
|
||||||
"raw-loader": "0.5.1",
|
"raw-loader": "0.5.1",
|
||||||
|
"react-dev-utils": "11.0.4",
|
||||||
"rimraf": "3.0.0",
|
"rimraf": "3.0.0",
|
||||||
"sinon": "3.2.1",
|
"sinon": "3.2.1",
|
||||||
"style-loader": "0.23.0",
|
"style-loader": "0.23.0",
|
||||||
"ts-loader": "6.2.2",
|
"ts-loader": "6.2.2",
|
||||||
"tslint": "5.11.0",
|
"tslint": "5.11.0",
|
||||||
"tslint-microsoft-contrib": "6.0.0",
|
"tslint-microsoft-contrib": "6.0.0",
|
||||||
"typescript": "4.2.3",
|
"typescript": "4.2.4",
|
||||||
"url-loader": "1.1.1",
|
"url-loader": "1.1.1",
|
||||||
"wait-on": "4.0.2",
|
"wait-on": "4.0.2",
|
||||||
"webpack": "4.43.0",
|
"webpack": "4.43.0",
|
||||||
"webpack-bundle-analyzer": "3.6.1",
|
"webpack-bundle-analyzer": "3.6.1",
|
||||||
"webpack-cli": "3.3.10",
|
"webpack-cli": "3.3.10",
|
||||||
"webpack-dev-server": "3.11.0",
|
"webpack-dev-server": "3.11.0"
|
||||||
"worker-loader": "2.0.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node --max-old-space-size=10196 node_modules/webpack-dev-server/bin/webpack-dev-server.js",
|
"start": "node --max-old-space-size=10196 node_modules/webpack-dev-server/bin/webpack-dev-server.js",
|
||||||
@@ -200,8 +202,8 @@
|
|||||||
"format:check": "prettier --check \"{src,test}/**/*.{ts,tsx,html}\" \"*.{js,html}\"",
|
"format:check": "prettier --check \"{src,test}/**/*.{ts,tsx,html}\" \"*.{js,html}\"",
|
||||||
"lint": "tslint --project tsconfig.json && eslint \"**/*.{ts,tsx}\"",
|
"lint": "tslint --project tsconfig.json && eslint \"**/*.{ts,tsx}\"",
|
||||||
"build:contracts": "npm run compile:contracts",
|
"build:contracts": "npm run compile:contracts",
|
||||||
"strictEligibleFiles": "node ./strict-migration-tools/index.js",
|
"strict:find": "node ./strict-null-checks/find.js",
|
||||||
"autoAddStrictEligibleFiles": "node ./strict-migration-tools/autoAdd.js",
|
"strict:add": "node ./strict-null-checks/auto-add.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"
|
"generateARMClients": "ts-node --compiler-options '{\"module\":\"commonjs\"}' utils/armClientGenerator/generator.ts"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const express = require("express");
|
const express = require("express");
|
||||||
const { createProxyMiddleware } = require("http-proxy-middleware");
|
const { createProxyMiddleware } = require("http-proxy-middleware");
|
||||||
const port = process.env.PORT || 3000;
|
const port = process.env.PORT || 3000;
|
||||||
|
const fetch = require("node-fetch");
|
||||||
|
|
||||||
const api = createProxyMiddleware("/api", {
|
const api = createProxyMiddleware("/api", {
|
||||||
target: "https://main.documentdb.ext.azure.com",
|
target: "https://main.documentdb.ext.azure.com",
|
||||||
@@ -39,6 +40,29 @@ const app = express();
|
|||||||
app.use(api);
|
app.use(api);
|
||||||
app.use(proxy);
|
app.use(proxy);
|
||||||
app.use(commit);
|
app.use(commit);
|
||||||
|
app.get("/pull/:pr(\\d+)", (req, res) => {
|
||||||
|
const pr = req.params.pr;
|
||||||
|
const [, query] = req.originalUrl.split("?");
|
||||||
|
const search = new URLSearchParams(query);
|
||||||
|
|
||||||
|
fetch("https://api.github.com/repos/Azure/cosmos-explorer/pulls/" + pr)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ head: { ref, sha } }) => {
|
||||||
|
const prUrl = new URL("https://github.com/Azure/cosmos-explorer/pull/" + pr);
|
||||||
|
prUrl.hash = ref;
|
||||||
|
search.set("feature.pr", prUrl.href);
|
||||||
|
|
||||||
|
const explorer = new URL("https://cosmos-explorer-preview.azurewebsites.net/commit/" + sha + "/explorer.html");
|
||||||
|
explorer.search = search.toString();
|
||||||
|
|
||||||
|
const portal = new URL("https://ms.portal.azure.com/");
|
||||||
|
portal.searchParams.set("dataExplorerSource", explorer.href);
|
||||||
|
|
||||||
|
return res.redirect(portal.href);
|
||||||
|
})
|
||||||
|
.catch(() => res.sendStatus(500));
|
||||||
|
});
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Example app listening on port: ${port}`);
|
console.log(`Example app listening on port: ${port}`);
|
||||||
});
|
});
|
||||||
|
|||||||
659
preview/package-lock.json
generated
659
preview/package-lock.json
generated
@@ -1,8 +1,658 @@
|
|||||||
{
|
{
|
||||||
"name": "preview",
|
"name": "cosmos-explorer-preview",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "cosmos-explorer-preview",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"http-proxy-middleware": "^1.1.0",
|
||||||
|
"node-fetch": "^2.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/http-proxy": {
|
||||||
|
"version": "1.17.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.5.tgz",
|
||||||
|
"integrity": "sha512-GNkDE7bTv6Sf8JbV2GksknKOsk7OznNYHSdrtvPJXO0qJ9odZig6IZKUi5RFGi6d1bf6dgIAe4uXi3DBc7069Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "14.14.37",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz",
|
||||||
|
"integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw=="
|
||||||
|
},
|
||||||
|
"node_modules/accepts": {
|
||||||
|
"version": "1.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||||
|
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-types": "~2.1.24",
|
||||||
|
"negotiator": "0.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/array-flatten": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||||
|
},
|
||||||
|
"node_modules/body-parser": {
|
||||||
|
"version": "1.19.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
|
||||||
|
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "3.1.0",
|
||||||
|
"content-type": "~1.0.4",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "~1.1.2",
|
||||||
|
"http-errors": "1.7.2",
|
||||||
|
"iconv-lite": "0.4.24",
|
||||||
|
"on-finished": "~2.3.0",
|
||||||
|
"qs": "6.7.0",
|
||||||
|
"raw-body": "2.4.0",
|
||||||
|
"type-is": "~1.6.17"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/braces": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||||
|
"dependencies": {
|
||||||
|
"fill-range": "^7.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bytes": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/camelcase": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/content-disposition": {
|
||||||
|
"version": "0.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
|
||||||
|
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "5.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/content-type": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie-signature": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
|
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||||
|
},
|
||||||
|
"node_modules/debug": {
|
||||||
|
"version": "2.6.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
|
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/depd": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||||
|
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/destroy": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||||
|
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||||
|
},
|
||||||
|
"node_modules/ee-first": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||||
|
},
|
||||||
|
"node_modules/encodeurl": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/escape-html": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
|
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||||
|
},
|
||||||
|
"node_modules/etag": {
|
||||||
|
"version": "1.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||||
|
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eventemitter3": {
|
||||||
|
"version": "4.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||||
|
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
|
||||||
|
},
|
||||||
|
"node_modules/express": {
|
||||||
|
"version": "4.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
|
||||||
|
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
|
||||||
|
"dependencies": {
|
||||||
|
"accepts": "~1.3.7",
|
||||||
|
"array-flatten": "1.1.1",
|
||||||
|
"body-parser": "1.19.0",
|
||||||
|
"content-disposition": "0.5.3",
|
||||||
|
"content-type": "~1.0.4",
|
||||||
|
"cookie": "0.4.0",
|
||||||
|
"cookie-signature": "1.0.6",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "~1.1.2",
|
||||||
|
"encodeurl": "~1.0.2",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"etag": "~1.8.1",
|
||||||
|
"finalhandler": "~1.1.2",
|
||||||
|
"fresh": "0.5.2",
|
||||||
|
"merge-descriptors": "1.0.1",
|
||||||
|
"methods": "~1.1.2",
|
||||||
|
"on-finished": "~2.3.0",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"path-to-regexp": "0.1.7",
|
||||||
|
"proxy-addr": "~2.0.5",
|
||||||
|
"qs": "6.7.0",
|
||||||
|
"range-parser": "~1.2.1",
|
||||||
|
"safe-buffer": "5.1.2",
|
||||||
|
"send": "0.17.1",
|
||||||
|
"serve-static": "1.14.1",
|
||||||
|
"setprototypeof": "1.1.1",
|
||||||
|
"statuses": "~1.5.0",
|
||||||
|
"type-is": "~1.6.18",
|
||||||
|
"utils-merge": "1.0.1",
|
||||||
|
"vary": "~1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fill-range": {
|
||||||
|
"version": "7.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||||
|
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"to-regex-range": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/finalhandler": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"encodeurl": "~1.0.2",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"on-finished": "~2.3.0",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"statuses": "~1.5.0",
|
||||||
|
"unpipe": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.13.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz",
|
||||||
|
"integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/forwarded": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||||
|
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fresh": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
|
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/http-errors": {
|
||||||
|
"version": "1.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
|
||||||
|
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
|
||||||
|
"dependencies": {
|
||||||
|
"depd": "~1.1.2",
|
||||||
|
"inherits": "2.0.3",
|
||||||
|
"setprototypeof": "1.1.1",
|
||||||
|
"statuses": ">= 1.5.0 < 2",
|
||||||
|
"toidentifier": "1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/http-proxy": {
|
||||||
|
"version": "1.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
|
||||||
|
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"eventemitter3": "^4.0.0",
|
||||||
|
"follow-redirects": "^1.0.0",
|
||||||
|
"requires-port": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/http-proxy-middleware": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-OnjU5vyVgcZVe2AjLJyMrk8YLNOC2lspCHirB5ldM+B/dwEfZ5bgVTrFyzE9R7xRWAP/i/FXtvIqKjTNEZBhBg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/http-proxy": "^1.17.5",
|
||||||
|
"camelcase": "^6.2.0",
|
||||||
|
"http-proxy": "^1.18.1",
|
||||||
|
"is-glob": "^4.0.1",
|
||||||
|
"is-plain-obj": "^3.0.0",
|
||||||
|
"micromatch": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/iconv-lite": {
|
||||||
|
"version": "0.4.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/inherits": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||||
|
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||||
|
},
|
||||||
|
"node_modules/ipaddr.js": {
|
||||||
|
"version": "1.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
|
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-extglob": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
|
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-glob": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
|
||||||
|
"dependencies": {
|
||||||
|
"is-extglob": "^2.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-number": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-plain-obj": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/media-typer": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
|
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/merge-descriptors": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||||
|
},
|
||||||
|
"node_modules/methods": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||||
|
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/micromatch": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"braces": "^3.0.1",
|
||||||
|
"picomatch": "^2.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||||
|
"bin": {
|
||||||
|
"mime": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.46.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz",
|
||||||
|
"integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz",
|
||||||
|
"integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.46.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ms": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||||
|
},
|
||||||
|
"node_modules/negotiator": {
|
||||||
|
"version": "0.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||||
|
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-fetch": {
|
||||||
|
"version": "2.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||||
|
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/on-finished": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||||
|
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
|
||||||
|
"dependencies": {
|
||||||
|
"ee-first": "1.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/parseurl": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/path-to-regexp": {
|
||||||
|
"version": "0.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||||
|
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||||
|
},
|
||||||
|
"node_modules/picomatch": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/proxy-addr": {
|
||||||
|
"version": "2.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
|
||||||
|
"integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
|
||||||
|
"dependencies": {
|
||||||
|
"forwarded": "~0.1.2",
|
||||||
|
"ipaddr.js": "1.9.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/qs": {
|
||||||
|
"version": "6.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||||
|
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/range-parser": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/raw-body": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "3.1.0",
|
||||||
|
"http-errors": "1.7.2",
|
||||||
|
"iconv-lite": "0.4.24",
|
||||||
|
"unpipe": "1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/requires-port": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
|
||||||
|
},
|
||||||
|
"node_modules/safe-buffer": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||||
|
},
|
||||||
|
"node_modules/safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
|
},
|
||||||
|
"node_modules/send": {
|
||||||
|
"version": "0.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
|
||||||
|
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "~1.1.2",
|
||||||
|
"destroy": "~1.0.4",
|
||||||
|
"encodeurl": "~1.0.2",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"etag": "~1.8.1",
|
||||||
|
"fresh": "0.5.2",
|
||||||
|
"http-errors": "~1.7.2",
|
||||||
|
"mime": "1.6.0",
|
||||||
|
"ms": "2.1.1",
|
||||||
|
"on-finished": "~2.3.0",
|
||||||
|
"range-parser": "~1.2.1",
|
||||||
|
"statuses": "~1.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/send/node_modules/ms": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||||
|
},
|
||||||
|
"node_modules/serve-static": {
|
||||||
|
"version": "1.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
|
||||||
|
"integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
|
||||||
|
"dependencies": {
|
||||||
|
"encodeurl": "~1.0.2",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"send": "0.17.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/setprototypeof": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
|
||||||
|
},
|
||||||
|
"node_modules/statuses": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||||
|
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/to-regex-range": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"is-number": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/toidentifier": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/type-is": {
|
||||||
|
"version": "1.6.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||||
|
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||||
|
"dependencies": {
|
||||||
|
"media-typer": "0.3.0",
|
||||||
|
"mime-types": "~2.1.24"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/unpipe": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/utils-merge": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vary": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/http-proxy": {
|
"@types/http-proxy": {
|
||||||
"version": "1.17.5",
|
"version": "1.17.5",
|
||||||
@@ -334,6 +984,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||||
},
|
},
|
||||||
|
"node-fetch": {
|
||||||
|
"version": "2.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||||
|
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
|
||||||
|
},
|
||||||
"on-finished": {
|
"on-finished": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"author": "Microsoft Corporation",
|
"author": "Microsoft Corporation",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"http-proxy-middleware": "^1.1.0"
|
"http-proxy-middleware": "^1.1.0",
|
||||||
|
"node-fetch": "^2.6.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
68
src/CellOutputViewer/CellOutputViewer.tsx
Normal file
68
src/CellOutputViewer/CellOutputViewer.tsx
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { createImmutableOutput, JSONObject, OnDiskOutput } from "@nteract/commutable";
|
||||||
|
// import outputs individually to avoid increasing the bundle size
|
||||||
|
import { KernelOutputError } from "@nteract/outputs/lib/components/kernel-output-error";
|
||||||
|
import { Output } from "@nteract/outputs/lib/components/output";
|
||||||
|
import { StreamText } from "@nteract/outputs/lib/components/stream-text";
|
||||||
|
import { ContentRef } from "@nteract/types";
|
||||||
|
import "bootstrap/dist/css/bootstrap.css";
|
||||||
|
import postRobot from "post-robot";
|
||||||
|
import * as React from "react";
|
||||||
|
import * as ReactDOM from "react-dom";
|
||||||
|
import "../../externals/iframeResizer.contentWindow.min.js"; // Required for iFrameResizer to work
|
||||||
|
import "../Explorer/Notebook/NotebookRenderer/base.css";
|
||||||
|
import "../Explorer/Notebook/NotebookRenderer/default.css";
|
||||||
|
import { TransformMedia } from "./TransformMedia";
|
||||||
|
|
||||||
|
export interface CellOutputViewerProps {
|
||||||
|
id: string;
|
||||||
|
contentRef: ContentRef;
|
||||||
|
hidden: boolean;
|
||||||
|
expanded: boolean;
|
||||||
|
outputs: OnDiskOutput[];
|
||||||
|
onMetadataChange: (metadata: JSONObject, mediaType: string, index?: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onInit = async () => {
|
||||||
|
postRobot.on(
|
||||||
|
"props",
|
||||||
|
{
|
||||||
|
window: window.parent,
|
||||||
|
domain: window.location.origin,
|
||||||
|
},
|
||||||
|
(event) => {
|
||||||
|
// Typescript definition for event is wrong. So read props by casting to <any>
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const props = (event as any).data as CellOutputViewerProps;
|
||||||
|
const outputs = (
|
||||||
|
<div
|
||||||
|
data-iframe-height
|
||||||
|
className={`nteract-cell-outputs ${props.hidden ? "hidden" : ""} ${props.expanded ? "expanded" : ""}`}
|
||||||
|
>
|
||||||
|
{props.outputs?.map((output, index) => (
|
||||||
|
<Output output={createImmutableOutput(output)} key={index}>
|
||||||
|
<TransformMedia
|
||||||
|
output_type={"display_data"}
|
||||||
|
id={props.id}
|
||||||
|
contentRef={props.contentRef}
|
||||||
|
onMetadataChange={(metadata, mediaType) => props.onMetadataChange(metadata, mediaType, index)}
|
||||||
|
/>
|
||||||
|
<TransformMedia
|
||||||
|
output_type={"execute_result"}
|
||||||
|
id={props.id}
|
||||||
|
contentRef={props.contentRef}
|
||||||
|
onMetadataChange={(metadata, mediaType) => props.onMetadataChange(metadata, mediaType, index)}
|
||||||
|
/>
|
||||||
|
<KernelOutputError />
|
||||||
|
<StreamText />
|
||||||
|
</Output>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
ReactDOM.render(outputs, document.getElementById("cellOutput"));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Entry point
|
||||||
|
window.addEventListener("load", onInit);
|
||||||
138
src/CellOutputViewer/TransformMedia.tsx
Normal file
138
src/CellOutputViewer/TransformMedia.tsx
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import { ImmutableDisplayData, ImmutableExecuteResult, JSONObject } from "@nteract/commutable";
|
||||||
|
// import outputs individually to avoid increasing the bundle size
|
||||||
|
import { HTML } from "@nteract/outputs/lib/components/media/html";
|
||||||
|
import { Image } from "@nteract/outputs/lib/components/media/image";
|
||||||
|
import { JavaScript } from "@nteract/outputs/lib/components/media/javascript";
|
||||||
|
import { Json } from "@nteract/outputs/lib/components/media/json";
|
||||||
|
import { LaTeX } from "@nteract/outputs/lib/components/media/latex";
|
||||||
|
import { Plain } from "@nteract/outputs/lib/components/media/plain";
|
||||||
|
import { SVG } from "@nteract/outputs/lib/components/media/svg";
|
||||||
|
import { ContentRef } from "@nteract/types";
|
||||||
|
import React, { Suspense } from "react";
|
||||||
|
|
||||||
|
const EmptyTransform = (): JSX.Element => <></>;
|
||||||
|
|
||||||
|
const displayOrder = [
|
||||||
|
"application/vnd.jupyter.widget-view+json",
|
||||||
|
"application/vnd.vega.v5+json",
|
||||||
|
"application/vnd.vega.v4+json",
|
||||||
|
"application/vnd.vega.v3+json",
|
||||||
|
"application/vnd.vega.v2+json",
|
||||||
|
"application/vnd.vegalite.v4+json",
|
||||||
|
"application/vnd.vegalite.v3+json",
|
||||||
|
"application/vnd.vegalite.v2+json",
|
||||||
|
"application/vnd.vegalite.v1+json",
|
||||||
|
"application/geo+json",
|
||||||
|
"application/vnd.plotly.v1+json",
|
||||||
|
"text/vnd.plotly.v1+html",
|
||||||
|
"application/x-nteract-model-debug+json",
|
||||||
|
"application/vnd.dataresource+json",
|
||||||
|
"application/vdom.v1+json",
|
||||||
|
"application/json",
|
||||||
|
"application/javascript",
|
||||||
|
"text/html",
|
||||||
|
"text/markdown",
|
||||||
|
"text/latex",
|
||||||
|
"image/svg+xml",
|
||||||
|
"image/gif",
|
||||||
|
"image/png",
|
||||||
|
"image/jpeg",
|
||||||
|
"text/plain",
|
||||||
|
];
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const transformsById = new Map<string, React.ComponentType<any>>([
|
||||||
|
["text/vnd.plotly.v1+html", React.lazy(() => import("@nteract/transform-plotly"))],
|
||||||
|
["application/vnd.plotly.v1+json", React.lazy(() => import("@nteract/transform-plotly"))],
|
||||||
|
["application/geo+json", EmptyTransform], // TODO: The geojson transform will likely need some work because of the basemap URL(s)
|
||||||
|
["application/x-nteract-model-debug+json", React.lazy(() => import("@nteract/transform-model-debug"))],
|
||||||
|
["application/vnd.dataresource+json", React.lazy(() => import("@nteract/data-explorer"))],
|
||||||
|
["application/vnd.jupyter.widget-view+json", React.lazy(() => import("./transforms/WidgetDisplay"))],
|
||||||
|
["application/vnd.vegalite.v1+json", React.lazy(() => import("./transforms/VegaLite1"))],
|
||||||
|
["application/vnd.vegalite.v2+json", React.lazy(() => import("./transforms/VegaLite2"))],
|
||||||
|
["application/vnd.vegalite.v3+json", React.lazy(() => import("./transforms/VegaLite3"))],
|
||||||
|
["application/vnd.vegalite.v4+json", React.lazy(() => import("./transforms/VegaLite4"))],
|
||||||
|
["application/vnd.vega.v2+json", React.lazy(() => import("./transforms/Vega2"))],
|
||||||
|
["application/vnd.vega.v3+json", React.lazy(() => import("./transforms/Vega3"))],
|
||||||
|
["application/vnd.vega.v4+json", React.lazy(() => import("./transforms/Vega4"))],
|
||||||
|
["application/vnd.vega.v5+json", React.lazy(() => import("./transforms/Vega5"))],
|
||||||
|
["application/vdom.v1+json", React.lazy(() => import("@nteract/transform-vdom"))],
|
||||||
|
["application/json", Json],
|
||||||
|
["application/javascript", JavaScript],
|
||||||
|
["text/html", HTML],
|
||||||
|
["text/markdown", React.lazy(() => import("@nteract/outputs/lib/components/media/markdown"))], // Markdown increases the bundle size so lazy load it
|
||||||
|
["text/latex", LaTeX],
|
||||||
|
["image/svg+xml", SVG],
|
||||||
|
["image/gif", Image],
|
||||||
|
["image/png", Image],
|
||||||
|
["image/jpeg", Image],
|
||||||
|
["text/plain", Plain],
|
||||||
|
]);
|
||||||
|
|
||||||
|
interface TransformMediaProps {
|
||||||
|
output_type: string;
|
||||||
|
id: string;
|
||||||
|
contentRef: ContentRef;
|
||||||
|
output?: ImmutableDisplayData | ImmutableExecuteResult;
|
||||||
|
onMetadataChange: (metadata: JSONObject, mediaType: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TransformMedia = (props: TransformMediaProps): JSX.Element => {
|
||||||
|
const { Media, mediaType, data, metadata } = getMediaInfo(props);
|
||||||
|
|
||||||
|
// If we had no valid result, return an empty output
|
||||||
|
if (!mediaType || !data) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
|
<Media
|
||||||
|
onMetadataChange={props.onMetadataChange}
|
||||||
|
data={data}
|
||||||
|
metadata={metadata}
|
||||||
|
contentRef={props.contentRef}
|
||||||
|
id={props.id}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMediaInfo = (props: TransformMediaProps) => {
|
||||||
|
const { output, output_type } = props;
|
||||||
|
// This component should only be used with display data and execute result
|
||||||
|
if (!output || !(output_type === "display_data" || output_type === "execute_result")) {
|
||||||
|
console.warn("connected transform media managed to get a non media bundle output");
|
||||||
|
return {
|
||||||
|
Media: EmptyTransform,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the first mediaType in the output data that we support with a handler
|
||||||
|
const mediaType = displayOrder.find(
|
||||||
|
(key) =>
|
||||||
|
Object.prototype.hasOwnProperty.call(output.data, key) &&
|
||||||
|
(Object.prototype.hasOwnProperty.call(transformsById, key) || transformsById.get(key))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mediaType) {
|
||||||
|
const metadata = output.metadata.get(mediaType);
|
||||||
|
const data = output.data[mediaType];
|
||||||
|
|
||||||
|
const Media = transformsById.get(mediaType);
|
||||||
|
return {
|
||||||
|
Media,
|
||||||
|
mediaType,
|
||||||
|
data,
|
||||||
|
metadata,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
Media: EmptyTransform,
|
||||||
|
mediaType,
|
||||||
|
output,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TransformMedia;
|
||||||
12
src/CellOutputViewer/cellOutputViewer.html
Normal file
12
src/CellOutputViewer/cellOutputViewer.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Cell Output Viewer</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="cellOutput" id="cellOutput"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1
src/CellOutputViewer/transforms/Vega2.ts
Normal file
1
src/CellOutputViewer/transforms/Vega2.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { Vega2 as default } from "@nteract/transform-vega";
|
||||||
1
src/CellOutputViewer/transforms/Vega3.ts
Normal file
1
src/CellOutputViewer/transforms/Vega3.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { Vega3 as default } from "@nteract/transform-vega";
|
||||||
1
src/CellOutputViewer/transforms/Vega4.ts
Normal file
1
src/CellOutputViewer/transforms/Vega4.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { Vega4 as default } from "@nteract/transform-vega";
|
||||||
1
src/CellOutputViewer/transforms/Vega5.ts
Normal file
1
src/CellOutputViewer/transforms/Vega5.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { Vega5 as default } from "@nteract/transform-vega";
|
||||||
1
src/CellOutputViewer/transforms/VegaLite1.ts
Normal file
1
src/CellOutputViewer/transforms/VegaLite1.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { VegaLite1 as default } from "@nteract/transform-vega";
|
||||||
1
src/CellOutputViewer/transforms/VegaLite2.ts
Normal file
1
src/CellOutputViewer/transforms/VegaLite2.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { VegaLite2 as default } from "@nteract/transform-vega";
|
||||||
1
src/CellOutputViewer/transforms/VegaLite3.ts
Normal file
1
src/CellOutputViewer/transforms/VegaLite3.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { VegaLite3 as default } from "@nteract/transform-vega";
|
||||||
1
src/CellOutputViewer/transforms/VegaLite4.ts
Normal file
1
src/CellOutputViewer/transforms/VegaLite4.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { VegaLite4 as default } from "@nteract/transform-vega";
|
||||||
1
src/CellOutputViewer/transforms/WidgetDisplay.ts
Normal file
1
src/CellOutputViewer/transforms/WidgetDisplay.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { WidgetDisplay as default } from "@nteract/jupyter-widgets";
|
||||||
@@ -75,7 +75,10 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _client: Cosmos.CosmosClient;
|
||||||
|
|
||||||
export function client(): Cosmos.CosmosClient {
|
export function client(): Cosmos.CosmosClient {
|
||||||
|
if (_client) return _client;
|
||||||
const options: Cosmos.CosmosClientOptions = {
|
const options: Cosmos.CosmosClientOptions = {
|
||||||
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
|
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
|
||||||
key: userContext.masterKey,
|
key: userContext.masterKey,
|
||||||
@@ -89,5 +92,6 @@ export function client(): Cosmos.CosmosClient {
|
|||||||
if (configContext.PROXY_PATH !== undefined) {
|
if (configContext.PROXY_PATH !== undefined) {
|
||||||
(options as any).plugins = [{ on: "request", plugin: requestPlugin }];
|
(options as any).plugins = [{ on: "request", plugin: requestPlugin }];
|
||||||
}
|
}
|
||||||
return new Cosmos.CosmosClient(options);
|
_client = new Cosmos.CosmosClient(options);
|
||||||
|
return _client;
|
||||||
}
|
}
|
||||||
|
|||||||
61
src/Common/EntityValue.tsx
Normal file
61
src/Common/EntityValue.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { DatePicker, TextField } from "office-ui-fabric-react";
|
||||||
|
import React, { FunctionComponent } from "react";
|
||||||
|
|
||||||
|
export interface TableEntityProps {
|
||||||
|
entityValueLabel?: string;
|
||||||
|
entityValuePlaceholder: string;
|
||||||
|
entityValue: string | Date;
|
||||||
|
isEntityTypeDate: boolean;
|
||||||
|
entityTimeValue: string;
|
||||||
|
entityValueType: string;
|
||||||
|
onEntityValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
|
||||||
|
onSelectDate: (date: Date | null | undefined) => void;
|
||||||
|
onEntityTimeValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EntityValue: FunctionComponent<TableEntityProps> = ({
|
||||||
|
entityValueLabel,
|
||||||
|
entityValuePlaceholder,
|
||||||
|
entityValue,
|
||||||
|
isEntityTypeDate,
|
||||||
|
entityTimeValue,
|
||||||
|
entityValueType,
|
||||||
|
onEntityValueChange,
|
||||||
|
onSelectDate,
|
||||||
|
onEntityTimeValueChange,
|
||||||
|
}: TableEntityProps): JSX.Element => {
|
||||||
|
if (isEntityTypeDate) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DatePicker
|
||||||
|
className="addEntityDatePicker"
|
||||||
|
placeholder={entityValuePlaceholder}
|
||||||
|
value={entityValue && new Date(entityValue)}
|
||||||
|
ariaLabel={entityValuePlaceholder}
|
||||||
|
onSelectDate={onSelectDate}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label={entityValueLabel && entityValueLabel}
|
||||||
|
id="entityTimeId"
|
||||||
|
autoFocus
|
||||||
|
type="time"
|
||||||
|
value={entityTimeValue}
|
||||||
|
onChange={onEntityTimeValueChange}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
label={entityValueLabel && entityValueLabel}
|
||||||
|
className="addEntityTextField"
|
||||||
|
id="entityValueId"
|
||||||
|
autoFocus
|
||||||
|
type={entityValueType}
|
||||||
|
placeholder={entityValuePlaceholder}
|
||||||
|
value={typeof entityValue === "string" && entityValue}
|
||||||
|
onChange={onEntityValueChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -48,32 +48,18 @@ export function sendCachedDataMessage<TResponseDataModel>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function sendMessage(data: any): void {
|
export function sendMessage(data: any): void {
|
||||||
if (canSendMessage()) {
|
_sendMessage({
|
||||||
// We try to find data explorer window first, then fallback to current window
|
signature: "pcIframe",
|
||||||
const portalChildWindow = getDataExplorerWindow(window) || window;
|
data: data,
|
||||||
portalChildWindow.parent.postMessage(
|
});
|
||||||
{
|
|
||||||
signature: "pcIframe",
|
|
||||||
data: data,
|
|
||||||
},
|
|
||||||
portalChildWindow.document.referrer || "*"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendReadyMessage(): void {
|
export function sendReadyMessage(): void {
|
||||||
if (canSendMessage()) {
|
_sendMessage({
|
||||||
// We try to find data explorer window first, then fallback to current window
|
signature: "pcIframe",
|
||||||
const portalChildWindow = getDataExplorerWindow(window) || window;
|
kind: "ready",
|
||||||
portalChildWindow.parent.postMessage(
|
data: "ready",
|
||||||
{
|
});
|
||||||
signature: "pcIframe",
|
|
||||||
kind: "ready",
|
|
||||||
data: "ready",
|
|
||||||
},
|
|
||||||
portalChildWindow.document.referrer || "*"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canSendMessage(): boolean {
|
export function canSendMessage(): boolean {
|
||||||
@@ -89,3 +75,17 @@ export function runGarbageCollector() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _sendMessage = (message: any): void => {
|
||||||
|
if (canSendMessage()) {
|
||||||
|
// Portal window can receive messages from only child windows
|
||||||
|
const portalChildWindow = getDataExplorerWindow(window) || window;
|
||||||
|
if (portalChildWindow === window) {
|
||||||
|
// Current window is a child of portal, send message to portal window
|
||||||
|
portalChildWindow.parent.postMessage(message, portalChildWindow.document.referrer || "*");
|
||||||
|
} else {
|
||||||
|
// Current window is not a child of portal, send message to the child window instead (which is data explorer)
|
||||||
|
portalChildWindow.postMessage(message, portalChildWindow.location.origin || "*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,11 +5,10 @@ import { configContext } from "../ConfigContext";
|
|||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
import { Collection } from "../Contracts/ViewModels";
|
import { Collection } from "../Contracts/ViewModels";
|
||||||
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
|
||||||
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
|
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
||||||
import { MinimalQueryIterator } from "./IteratorUtilities";
|
import { MinimalQueryIterator } from "./IteratorUtilities";
|
||||||
import { sendMessage } from "./MessageHandler";
|
import { sendMessage } from "./MessageHandler";
|
||||||
|
|
||||||
@@ -348,10 +347,7 @@ export function getEndpoint(): string {
|
|||||||
async function errorHandling(response: Response, action: string, params: unknown): Promise<void> {
|
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(
|
logConsoleError(`Error ${action}: ${errorMessage}, Payload: ${JSON.stringify(params)}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error ${action}: ${errorMessage}, Payload: ${JSON.stringify(params)}`
|
|
||||||
);
|
|
||||||
if (response.status === HttpStatusCodes.Forbidden) {
|
if (response.status === HttpStatusCodes.Forbidden) {
|
||||||
sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage });
|
sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage });
|
||||||
return;
|
return;
|
||||||
|
|||||||
136
src/Common/TableEntity.tsx
Normal file
136
src/Common/TableEntity.tsx
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import {
|
||||||
|
Dropdown,
|
||||||
|
IDropdownOption,
|
||||||
|
IDropdownStyles,
|
||||||
|
IImageProps,
|
||||||
|
Image,
|
||||||
|
IStackTokens,
|
||||||
|
Stack,
|
||||||
|
TextField,
|
||||||
|
TooltipHost,
|
||||||
|
} from "office-ui-fabric-react";
|
||||||
|
import React, { FunctionComponent } from "react";
|
||||||
|
import DeleteIcon from "../../images/delete.svg";
|
||||||
|
import EditIcon from "../../images/Edit_entity.svg";
|
||||||
|
import { CassandraType, TableType } from "../Explorer/Tables/Constants";
|
||||||
|
import { userContext } from "../UserContext";
|
||||||
|
import { EntityValue } from "./EntityValue";
|
||||||
|
|
||||||
|
const dropdownStyles: Partial<IDropdownStyles> = { dropdown: { width: 100 } };
|
||||||
|
|
||||||
|
export interface TableEntityProps {
|
||||||
|
entityTypeLabel?: string;
|
||||||
|
entityPropertyLabel?: string;
|
||||||
|
entityValueLabel?: string;
|
||||||
|
isDeleteOptionVisible: boolean;
|
||||||
|
entityProperty: string;
|
||||||
|
entityPropertyPlaceHolder: string;
|
||||||
|
selectedKey: string | number;
|
||||||
|
entityValuePlaceholder: string;
|
||||||
|
entityValue: string | Date;
|
||||||
|
isEntityTypeDate: boolean;
|
||||||
|
options: { key: string; text: string }[];
|
||||||
|
isPropertyTypeDisable: boolean;
|
||||||
|
entityTimeValue: string;
|
||||||
|
onDeleteEntity?: () => void;
|
||||||
|
onEditEntity?: () => void;
|
||||||
|
onEntityPropertyChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
|
||||||
|
onEntityTypeChange: (event: React.FormEvent<HTMLElement>, selectedParam: IDropdownOption) => void;
|
||||||
|
onEntityValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
|
||||||
|
onSelectDate: (date: Date | null | undefined) => void;
|
||||||
|
onEntityTimeValueChange: (event: React.FormEvent<HTMLElement>, newInput?: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TableEntity: FunctionComponent<TableEntityProps> = ({
|
||||||
|
entityTypeLabel,
|
||||||
|
entityPropertyLabel,
|
||||||
|
isDeleteOptionVisible,
|
||||||
|
entityProperty,
|
||||||
|
selectedKey,
|
||||||
|
entityPropertyPlaceHolder,
|
||||||
|
entityValueLabel,
|
||||||
|
entityValuePlaceholder,
|
||||||
|
entityValue,
|
||||||
|
options,
|
||||||
|
isPropertyTypeDisable,
|
||||||
|
isEntityTypeDate,
|
||||||
|
entityTimeValue,
|
||||||
|
onEditEntity,
|
||||||
|
onDeleteEntity,
|
||||||
|
onEntityPropertyChange,
|
||||||
|
onEntityTypeChange,
|
||||||
|
onEntityValueChange,
|
||||||
|
onSelectDate,
|
||||||
|
onEntityTimeValueChange,
|
||||||
|
}: TableEntityProps): JSX.Element => {
|
||||||
|
const imageProps: IImageProps = {
|
||||||
|
width: 16,
|
||||||
|
height: 30,
|
||||||
|
className: entityPropertyLabel ? "addRemoveIconLabel" : "addRemoveIcon",
|
||||||
|
};
|
||||||
|
|
||||||
|
const sectionStackTokens: IStackTokens = { childrenGap: 12 };
|
||||||
|
|
||||||
|
const getEntityValueType = (): string => {
|
||||||
|
const { Int, Smallint, Tinyint } = CassandraType;
|
||||||
|
const { Double, Int32, Int64 } = TableType;
|
||||||
|
|
||||||
|
if (
|
||||||
|
selectedKey === Double ||
|
||||||
|
selectedKey === Int32 ||
|
||||||
|
selectedKey === Int64 ||
|
||||||
|
selectedKey === Int ||
|
||||||
|
selectedKey === Smallint ||
|
||||||
|
selectedKey === Tinyint
|
||||||
|
) {
|
||||||
|
return "number";
|
||||||
|
}
|
||||||
|
return "string";
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Stack horizontal tokens={sectionStackTokens}>
|
||||||
|
<TextField
|
||||||
|
label={entityPropertyLabel && entityPropertyLabel}
|
||||||
|
id="entityPropertyId"
|
||||||
|
autoFocus
|
||||||
|
disabled={isPropertyTypeDisable}
|
||||||
|
placeholder={entityPropertyPlaceHolder}
|
||||||
|
value={entityProperty}
|
||||||
|
onChange={onEntityPropertyChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Dropdown
|
||||||
|
label={entityTypeLabel && entityTypeLabel}
|
||||||
|
selectedKey={selectedKey}
|
||||||
|
onChange={onEntityTypeChange}
|
||||||
|
options={options}
|
||||||
|
disabled={isPropertyTypeDisable}
|
||||||
|
id="entityTypeId"
|
||||||
|
styles={dropdownStyles}
|
||||||
|
/>
|
||||||
|
<EntityValue
|
||||||
|
entityValueLabel={entityValueLabel}
|
||||||
|
entityValueType={getEntityValueType()}
|
||||||
|
entityValuePlaceholder={entityValuePlaceholder}
|
||||||
|
entityValue={entityValue}
|
||||||
|
isEntityTypeDate={isEntityTypeDate}
|
||||||
|
entityTimeValue={entityTimeValue}
|
||||||
|
onEntityValueChange={onEntityValueChange}
|
||||||
|
onSelectDate={onSelectDate}
|
||||||
|
onEntityTimeValueChange={onEntityTimeValueChange}
|
||||||
|
/>
|
||||||
|
<TooltipHost content="Edit property" id="editTooltip">
|
||||||
|
<Image {...imageProps} src={EditIcon} alt="editEntity" id="editEntity" onClick={onEditEntity} />
|
||||||
|
</TooltipHost>
|
||||||
|
|
||||||
|
{isDeleteOptionVisible && userContext.apiType !== "Cassandra" && (
|
||||||
|
<TooltipHost content="Delete property" id="deleteTooltip">
|
||||||
|
<Image {...imageProps} src={DeleteIcon} alt="delete entity" id="deleteEntity" onClick={onDeleteEntity} />
|
||||||
|
</TooltipHost>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Image, Stack, TextField } from "office-ui-fabric-react";
|
import { Image, Stack, TextField } from "office-ui-fabric-react";
|
||||||
import React, { ChangeEvent, FunctionComponent, KeyboardEvent, useRef, useState } from "react";
|
import React, { ChangeEvent, FunctionComponent, KeyboardEvent, useRef, useState } from "react";
|
||||||
import FolderIcon from "../../../images/folder_16x16.svg";
|
import FolderIcon from "../../../images/folder_16x16.svg";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../Constants";
|
||||||
import { Tooltip } from "../Tooltip";
|
import { Tooltip } from "../Tooltip/Tooltip";
|
||||||
|
|
||||||
interface UploadProps {
|
interface UploadProps {
|
||||||
label: string;
|
label: string;
|
||||||
39
src/Common/dataAccess/bulkCreateDocument.ts
Normal file
39
src/Common/dataAccess/bulkCreateDocument.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { JSONObject, OperationResponse } from "@azure/cosmos";
|
||||||
|
import { CollectionBase } from "../../Contracts/ViewModels";
|
||||||
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
|
||||||
|
export const bulkCreateDocument = async (
|
||||||
|
collection: CollectionBase,
|
||||||
|
documents: JSONObject[]
|
||||||
|
): Promise<OperationResponse[]> => {
|
||||||
|
const clearMessage = logConsoleProgress(
|
||||||
|
`Executing ${documents.length} bulk operations for container ${collection.id()}`
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await client()
|
||||||
|
.database(collection.databaseId)
|
||||||
|
.container(collection.id())
|
||||||
|
.items.bulk(
|
||||||
|
documents.map((doc) => ({ operationType: "Create", resourceBody: doc })),
|
||||||
|
{ continueOnError: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const successCount = response.filter((r) => r.statusCode === 201).length;
|
||||||
|
const throttledCount = response.filter((r) => r.statusCode === 429).length;
|
||||||
|
|
||||||
|
logConsoleInfo(
|
||||||
|
`${
|
||||||
|
documents.length
|
||||||
|
} operations completed for container ${collection.id()}. ${successCount} operations succeeded. ${throttledCount} operations throttled`
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, "BulkCreateDocument", `Error bulk creating items for container ${collection.id()}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
clearMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
import * as DataModels from "../../Contracts/DataModels";
|
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import { client } from "../CosmosClient";
|
import { userContext } from "../../UserContext";
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
|
||||||
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
|
||||||
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
|
||||||
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
|
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
|
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { userContext } from "../../UserContext";
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
|
||||||
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
|
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
|
||||||
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
||||||
@@ -17,7 +17,6 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
|
|||||||
if (
|
if (
|
||||||
userContext.authType === AuthType.AAD &&
|
userContext.authType === AuthType.AAD &&
|
||||||
!userContext.useSDKOperations &&
|
!userContext.useSDKOperations &&
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||||
) {
|
) {
|
||||||
return await readCollectionsWithARM(databaseId);
|
return await readCollectionsWithARM(databaseId);
|
||||||
|
|||||||
@@ -1,39 +1,37 @@
|
|||||||
|
import { ContainerDefinition } from "@azure/cosmos";
|
||||||
|
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { Collection } from "../../Contracts/DataModels";
|
import { Collection } from "../../Contracts/DataModels";
|
||||||
import { ContainerDefinition } from "@azure/cosmos";
|
|
||||||
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
import {
|
import { userContext } from "../../UserContext";
|
||||||
CreateUpdateOptions,
|
|
||||||
ExtendedResourceProperties,
|
|
||||||
MongoDBCollectionCreateUpdateParameters,
|
|
||||||
MongoDBCollectionResource,
|
|
||||||
SqlContainerCreateUpdateParameters,
|
|
||||||
SqlContainerResource,
|
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
|
||||||
import { RequestOptions } from "@azure/cosmos/dist-esm";
|
|
||||||
import { client } from "../CosmosClient";
|
|
||||||
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
|
||||||
import {
|
import {
|
||||||
createUpdateCassandraTable,
|
createUpdateCassandraTable,
|
||||||
getCassandraTable,
|
getCassandraTable,
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
|
||||||
import {
|
|
||||||
createUpdateMongoDBCollection,
|
|
||||||
getMongoDBCollection,
|
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
|
||||||
import {
|
import {
|
||||||
createUpdateGremlinGraph,
|
createUpdateGremlinGraph,
|
||||||
getGremlinGraph,
|
getGremlinGraph,
|
||||||
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
|
||||||
|
import {
|
||||||
|
createUpdateMongoDBCollection,
|
||||||
|
getMongoDBCollection,
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
|
||||||
|
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
|
||||||
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
|
||||||
import { handleError } from "../ErrorHandlingUtils";
|
import {
|
||||||
|
ExtendedResourceProperties,
|
||||||
|
MongoDBCollectionCreateUpdateParameters,
|
||||||
|
SqlContainerCreateUpdateParameters,
|
||||||
|
SqlContainerResource,
|
||||||
|
} from "../../Utils/arm/generatedClients/2020-04-01/types";
|
||||||
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { userContext } from "../../UserContext";
|
import { client } from "../CosmosClient";
|
||||||
|
import { handleError } from "../ErrorHandlingUtils";
|
||||||
|
|
||||||
export async function updateCollection(
|
export async function updateCollection(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
newCollection: Collection,
|
newCollection: Partial<Collection>,
|
||||||
options: RequestOptions = {}
|
options: RequestOptions = {}
|
||||||
): Promise<Collection> {
|
): Promise<Collection> {
|
||||||
let collection: Collection;
|
let collection: Collection;
|
||||||
@@ -43,7 +41,6 @@ export async function updateCollection(
|
|||||||
if (
|
if (
|
||||||
userContext.authType === AuthType.AAD &&
|
userContext.authType === AuthType.AAD &&
|
||||||
!userContext.useSDKOperations &&
|
!userContext.useSDKOperations &&
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
|
|
||||||
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
userContext.defaultExperience !== DefaultAccountExperienceType.Table
|
||||||
) {
|
) {
|
||||||
collection = await updateCollectionWithARM(databaseId, collectionId, newCollection);
|
collection = await updateCollectionWithARM(databaseId, collectionId, newCollection);
|
||||||
@@ -69,7 +66,7 @@ export async function updateCollection(
|
|||||||
async function updateCollectionWithARM(
|
async function updateCollectionWithARM(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
newCollection: Collection
|
newCollection: Partial<Collection>
|
||||||
): Promise<Collection> {
|
): Promise<Collection> {
|
||||||
const subscriptionId = userContext.subscriptionId;
|
const subscriptionId = userContext.subscriptionId;
|
||||||
const resourceGroup = userContext.resourceGroup;
|
const resourceGroup = userContext.resourceGroup;
|
||||||
@@ -85,6 +82,15 @@ async function updateCollectionWithARM(
|
|||||||
return updateGremlinGraph(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
return updateGremlinGraph(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
||||||
case DefaultAccountExperienceType.Table:
|
case DefaultAccountExperienceType.Table:
|
||||||
return updateTable(collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
return updateTable(collectionId, subscriptionId, resourceGroup, accountName, newCollection);
|
||||||
|
case DefaultAccountExperienceType.MongoDB:
|
||||||
|
return updateMongoDBCollection(
|
||||||
|
databaseId,
|
||||||
|
collectionId,
|
||||||
|
subscriptionId,
|
||||||
|
resourceGroup,
|
||||||
|
accountName,
|
||||||
|
newCollection
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
|
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
|
||||||
}
|
}
|
||||||
@@ -96,7 +102,7 @@ async function updateSqlContainer(
|
|||||||
subscriptionId: string,
|
subscriptionId: string,
|
||||||
resourceGroup: string,
|
resourceGroup: string,
|
||||||
accountName: string,
|
accountName: string,
|
||||||
newCollection: Collection
|
newCollection: Partial<Collection>
|
||||||
): Promise<Collection> {
|
): Promise<Collection> {
|
||||||
const getResponse = await getSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
const getResponse = await getSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||||
@@ -115,35 +121,26 @@ async function updateSqlContainer(
|
|||||||
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
|
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateMongoDBCollectionThroughRP(
|
export async function updateMongoDBCollection(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
newCollection: MongoDBCollectionResource,
|
subscriptionId: string,
|
||||||
updateOptions?: CreateUpdateOptions
|
resourceGroup: string,
|
||||||
): Promise<MongoDBCollectionResource> {
|
accountName: string,
|
||||||
const subscriptionId = userContext.subscriptionId;
|
newCollection: Partial<Collection>
|
||||||
const resourceGroup = userContext.resourceGroup;
|
): Promise<Collection> {
|
||||||
const accountName = userContext.databaseAccount.name;
|
|
||||||
|
|
||||||
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||||
const updateParams: MongoDBCollectionCreateUpdateParameters = {
|
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
|
||||||
properties: {
|
|
||||||
resource: newCollection,
|
|
||||||
options: updateOptions,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateResponse = await createUpdateMongoDBCollection(
|
const updateResponse = await createUpdateMongoDBCollection(
|
||||||
subscriptionId,
|
subscriptionId,
|
||||||
resourceGroup,
|
resourceGroup,
|
||||||
accountName,
|
accountName,
|
||||||
databaseId,
|
databaseId,
|
||||||
collectionId,
|
collectionId,
|
||||||
updateParams
|
getResponse as MongoDBCollectionCreateUpdateParameters
|
||||||
);
|
);
|
||||||
|
return updateResponse && (updateResponse.properties.resource as Collection);
|
||||||
return updateResponse && (updateResponse.properties.resource as MongoDBCollectionResource);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -157,7 +154,7 @@ async function updateCassandraTable(
|
|||||||
subscriptionId: string,
|
subscriptionId: string,
|
||||||
resourceGroup: string,
|
resourceGroup: string,
|
||||||
accountName: string,
|
accountName: string,
|
||||||
newCollection: Collection
|
newCollection: Partial<Collection>
|
||||||
): Promise<Collection> {
|
): Promise<Collection> {
|
||||||
const getResponse = await getCassandraTable(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
const getResponse = await getCassandraTable(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||||
@@ -184,7 +181,7 @@ async function updateGremlinGraph(
|
|||||||
subscriptionId: string,
|
subscriptionId: string,
|
||||||
resourceGroup: string,
|
resourceGroup: string,
|
||||||
accountName: string,
|
accountName: string,
|
||||||
newCollection: Collection
|
newCollection: Partial<Collection>
|
||||||
): Promise<Collection> {
|
): Promise<Collection> {
|
||||||
const getResponse = await getGremlinGraph(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
const getResponse = await getGremlinGraph(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
|
||||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||||
@@ -208,7 +205,7 @@ async function updateTable(
|
|||||||
subscriptionId: string,
|
subscriptionId: string,
|
||||||
resourceGroup: string,
|
resourceGroup: string,
|
||||||
accountName: string,
|
accountName: string,
|
||||||
newCollection: Collection
|
newCollection: Partial<Collection>
|
||||||
): Promise<Collection> {
|
): Promise<Collection> {
|
||||||
const getResponse = await getTable(subscriptionId, resourceGroup, accountName, collectionId);
|
const getResponse = await getTable(subscriptionId, resourceGroup, accountName, collectionId);
|
||||||
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
if (getResponse && getResponse.properties && getResponse.properties.resource) {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export interface ConfigContext {
|
|||||||
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
|
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
|
||||||
hostedExplorerURL: string;
|
hostedExplorerURL: string;
|
||||||
armAPIVersion?: string;
|
armAPIVersion?: string;
|
||||||
|
allowedJunoOrigins: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default configuration
|
// Default configuration
|
||||||
@@ -53,6 +54,13 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
|
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
|
||||||
JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
|
JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
|
||||||
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
||||||
|
allowedJunoOrigins: [
|
||||||
|
"https://juno-test.documents-dev.windows-int.net",
|
||||||
|
"https://juno-test2.documents-dev.windows-int.net",
|
||||||
|
"https://tools.cosmos.azure.com",
|
||||||
|
"https://tools-staging.cosmos.azure.com",
|
||||||
|
"https://localhost",
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function resetConfigContext(): void {
|
export function resetConfigContext(): void {
|
||||||
@@ -86,13 +94,18 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
|
|||||||
});
|
});
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
try {
|
try {
|
||||||
const { allowedParentFrameOrigins, ...externalConfig } = await response.json();
|
const { allowedParentFrameOrigins, allowedJunoOrigins, ...externalConfig } = await response.json();
|
||||||
Object.assign(configContext, externalConfig);
|
Object.assign(configContext, externalConfig);
|
||||||
if (allowedParentFrameOrigins && allowedParentFrameOrigins.length > 0) {
|
if (allowedParentFrameOrigins && allowedParentFrameOrigins.length > 0) {
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
allowedParentFrameOrigins: [...configContext.allowedParentFrameOrigins, ...allowedParentFrameOrigins],
|
allowedParentFrameOrigins: [...configContext.allowedParentFrameOrigins, ...allowedParentFrameOrigins],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (allowedJunoOrigins && allowedJunoOrigins.length > 0) {
|
||||||
|
updateConfigContext({
|
||||||
|
allowedJunoOrigins: [...configContext.allowedJunoOrigins, ...allowedJunoOrigins],
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Unable to parse json in config file");
|
console.error("Unable to parse json in config file");
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
export enum TabKind {
|
export enum TabKind {
|
||||||
SQLDocuments,
|
SQLDocuments,
|
||||||
MongoDocuments,
|
MongoDocuments,
|
||||||
|
SchemaAnalyzer,
|
||||||
TableEntities,
|
TableEntities,
|
||||||
Graph,
|
Graph,
|
||||||
SQLQuery,
|
SQLQuery,
|
||||||
|
|||||||
@@ -121,6 +121,10 @@ export interface ISchemaRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Collection extends Resource {
|
export interface Collection extends Resource {
|
||||||
|
// Only in Mongo collections loaded via ARM
|
||||||
|
shardKey?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
defaultTtl?: number;
|
defaultTtl?: number;
|
||||||
indexingPolicy?: IndexingPolicy;
|
indexingPolicy?: IndexingPolicy;
|
||||||
partitionKey?: PartitionKey;
|
partitionKey?: PartitionKey;
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import StoredProcedure from "../Explorer/Tree/StoredProcedure";
|
|||||||
import Trigger from "../Explorer/Tree/Trigger";
|
import Trigger from "../Explorer/Tree/Trigger";
|
||||||
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
|
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
|
||||||
import { SelfServeType } from "../SelfServe/SelfServeUtils";
|
import { SelfServeType } from "../SelfServe/SelfServeUtils";
|
||||||
import { UploadDetails } from "../workers/upload/definitions";
|
|
||||||
import * as DataModels from "./DataModels";
|
import * as DataModels from "./DataModels";
|
||||||
import { SubscriptionType } from "./SubscriptionType";
|
import { SubscriptionType } from "./SubscriptionType";
|
||||||
|
|
||||||
@@ -23,6 +22,14 @@ export interface TokenProvider {
|
|||||||
getAuthHeader(): Promise<Headers>;
|
getAuthHeader(): Promise<Headers>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UploadDetailsRecord {
|
||||||
|
fileName: string;
|
||||||
|
numSucceeded: number;
|
||||||
|
numFailed: number;
|
||||||
|
numThrottled: number;
|
||||||
|
errors: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface QueryResultsMetadata {
|
export interface QueryResultsMetadata {
|
||||||
hasMoreResults: boolean;
|
hasMoreResults: boolean;
|
||||||
firstItemIndex: number;
|
firstItemIndex: number;
|
||||||
@@ -134,6 +141,7 @@ export interface Collection extends CollectionBase {
|
|||||||
onTableEntitiesClick(): void;
|
onTableEntitiesClick(): void;
|
||||||
onGraphDocumentsClick(): void;
|
onGraphDocumentsClick(): void;
|
||||||
onMongoDBDocumentsClick(): void;
|
onMongoDBDocumentsClick(): void;
|
||||||
|
onSchemaAnalyzerClick(): void;
|
||||||
openTab(): void;
|
openTab(): void;
|
||||||
|
|
||||||
onSettingsClick: () => Promise<void>;
|
onSettingsClick: () => Promise<void>;
|
||||||
@@ -174,7 +182,7 @@ export interface Collection extends CollectionBase {
|
|||||||
|
|
||||||
onDragOver(source: Collection, event: { originalEvent: DragEvent }): void;
|
onDragOver(source: Collection, event: { originalEvent: DragEvent }): void;
|
||||||
onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
|
onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
|
||||||
uploadFiles(fileList: FileList): Promise<UploadDetails>;
|
uploadFiles(fileList: FileList): Promise<{ data: UploadDetailsRecord[] }>;
|
||||||
|
|
||||||
getLabel(): string;
|
getLabel(): string;
|
||||||
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
|
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
|
||||||
@@ -269,7 +277,6 @@ export interface TabOptions {
|
|||||||
tabKind: CollectionTabKind;
|
tabKind: CollectionTabKind;
|
||||||
title: string;
|
title: string;
|
||||||
tabPath: string;
|
tabPath: string;
|
||||||
isActive: ko.Observable<boolean>;
|
|
||||||
hashLocation: string;
|
hashLocation: string;
|
||||||
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void;
|
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void;
|
||||||
isTabsContentExpanded?: ko.Observable<boolean>;
|
isTabsContentExpanded?: ko.Observable<boolean>;
|
||||||
@@ -360,6 +367,7 @@ export enum CollectionTabKind {
|
|||||||
Schema = 19,
|
Schema = 19,
|
||||||
CollectionSettingsV2 = 20,
|
CollectionSettingsV2 = 20,
|
||||||
DatabaseSettingsV2 = 21,
|
DatabaseSettingsV2 = 21,
|
||||||
|
SchemaAnalyzer = 22,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TerminalKind {
|
export enum TerminalKind {
|
||||||
@@ -390,6 +398,9 @@ export interface DataExplorerInputsFrame {
|
|||||||
dataExplorerVersion?: string;
|
dataExplorerVersion?: string;
|
||||||
defaultCollectionThroughput?: CollectionCreationDefaults;
|
defaultCollectionThroughput?: CollectionCreationDefaults;
|
||||||
flights?: readonly string[];
|
flights?: readonly string[];
|
||||||
|
features?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SelfServeFrameInputs {
|
export interface SelfServeFrameInputs {
|
||||||
|
|||||||
7
src/Definitions/worker.d.ts
vendored
7
src/Definitions/worker.d.ts
vendored
@@ -1,7 +0,0 @@
|
|||||||
declare module "worker-loader!*" {
|
|
||||||
class WebpackWorker extends Worker {
|
|
||||||
constructor();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WebpackWorker;
|
|
||||||
}
|
|
||||||
@@ -8,10 +8,6 @@ describe("Component Registerer", () => {
|
|||||||
expect(ko.components.isRegistered("input-typeahead")).toBe(true);
|
expect(ko.components.isRegistered("input-typeahead")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should register new-vertex-form component", () => {
|
|
||||||
expect(ko.components.isRegistered("new-vertex-form")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should register error-display component", () => {
|
it("should register error-display component", () => {
|
||||||
expect(ko.components.isRegistered("error-display")).toBe(true);
|
expect(ko.components.isRegistered("error-display")).toBe(true);
|
||||||
});
|
});
|
||||||
@@ -73,14 +69,6 @@ describe("Component Registerer", () => {
|
|||||||
expect(ko.components.isRegistered("add-collection-pane")).toBe(true);
|
expect(ko.components.isRegistered("add-collection-pane")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should register delete-collection-confirmation-pane component", () => {
|
|
||||||
expect(ko.components.isRegistered("delete-collection-confirmation-pane")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should register graph-new-vertex-pane component", () => {
|
|
||||||
expect(ko.components.isRegistered("graph-new-vertex-pane")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should register graph-styling-pane component", () => {
|
it("should register graph-styling-pane component", () => {
|
||||||
expect(ko.components.isRegistered("graph-styling-pane")).toBe(true);
|
expect(ko.components.isRegistered("graph-styling-pane")).toBe(true);
|
||||||
});
|
});
|
||||||
@@ -89,10 +77,6 @@ describe("Component Registerer", () => {
|
|||||||
expect(ko.components.isRegistered("string-input-pane")).toBe(true);
|
expect(ko.components.isRegistered("string-input-pane")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should register setup-notebooks-pane component", () => {
|
|
||||||
expect(ko.components.isRegistered("setup-notebooks-pane")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should register dynamic-list component", () => {
|
it("should register dynamic-list component", () => {
|
||||||
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
|
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahea
|
|||||||
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
|
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
|
||||||
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
||||||
import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleComponent";
|
import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleComponent";
|
||||||
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
|
|
||||||
import * as PaneComponents from "./Panes/PaneComponents";
|
import * as PaneComponents from "./Panes/PaneComponents";
|
||||||
import ConflictsTab from "./Tabs/ConflictsTab";
|
import ConflictsTab from "./Tabs/ConflictsTab";
|
||||||
import DocumentsTab from "./Tabs/DocumentsTab";
|
import DocumentsTab from "./Tabs/DocumentsTab";
|
||||||
@@ -18,15 +17,14 @@ import NotebookTabV2 from "./Tabs/NotebookV2Tab";
|
|||||||
import NotebookViewerTab from "./Tabs/NotebookViewerTab";
|
import NotebookViewerTab from "./Tabs/NotebookViewerTab";
|
||||||
import QueryTab from "./Tabs/QueryTab";
|
import QueryTab from "./Tabs/QueryTab";
|
||||||
import QueryTablesTab from "./Tabs/QueryTablesTab";
|
import QueryTablesTab from "./Tabs/QueryTablesTab";
|
||||||
|
import SchemaAnalyzerTab from "./Tabs/SchemaAnalyzerTab";
|
||||||
import { DatabaseSettingsTabV2, SettingsTabV2 } from "./Tabs/SettingsTabV2";
|
import { DatabaseSettingsTabV2, SettingsTabV2 } from "./Tabs/SettingsTabV2";
|
||||||
import StoredProcedureTab from "./Tabs/StoredProcedureTab";
|
import StoredProcedureTab from "./Tabs/StoredProcedureTab";
|
||||||
import TabsManagerTemplate from "./Tabs/TabsManager.html";
|
|
||||||
import TerminalTab from "./Tabs/TerminalTab";
|
import TerminalTab from "./Tabs/TerminalTab";
|
||||||
import TriggerTab from "./Tabs/TriggerTab";
|
import TriggerTab from "./Tabs/TriggerTab";
|
||||||
import UserDefinedFunctionTab from "./Tabs/UserDefinedFunctionTab";
|
import UserDefinedFunctionTab from "./Tabs/UserDefinedFunctionTab";
|
||||||
|
|
||||||
ko.components.register("input-typeahead", new InputTypeaheadComponent());
|
ko.components.register("input-typeahead", new InputTypeaheadComponent());
|
||||||
ko.components.register("new-vertex-form", NewVertexComponent);
|
|
||||||
ko.components.register("error-display", new ErrorDisplayComponent());
|
ko.components.register("error-display", new ErrorDisplayComponent());
|
||||||
ko.components.register("graph-style", GraphStyleComponent);
|
ko.components.register("graph-style", GraphStyleComponent);
|
||||||
ko.components.register("editor", new EditorComponent());
|
ko.components.register("editor", new EditorComponent());
|
||||||
@@ -34,7 +32,6 @@ ko.components.register("json-editor", new JsonEditorComponent());
|
|||||||
ko.components.register("diff-editor", new DiffEditorComponent());
|
ko.components.register("diff-editor", new DiffEditorComponent());
|
||||||
ko.components.register("dynamic-list", DynamicListComponent);
|
ko.components.register("dynamic-list", DynamicListComponent);
|
||||||
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
|
||||||
ko.components.register("tabs-manager", { template: TabsManagerTemplate });
|
|
||||||
|
|
||||||
// Collection Tabs
|
// Collection Tabs
|
||||||
[
|
[
|
||||||
@@ -53,22 +50,15 @@ ko.components.register("tabs-manager", { template: TabsManagerTemplate });
|
|||||||
GalleryTab,
|
GalleryTab,
|
||||||
NotebookViewerTab,
|
NotebookViewerTab,
|
||||||
DatabaseSettingsTabV2,
|
DatabaseSettingsTabV2,
|
||||||
|
SchemaAnalyzerTab,
|
||||||
].forEach(({ component: { name, template } }) => ko.components.register(name, { template }));
|
].forEach(({ component: { name, template } }) => ko.components.register(name, { template }));
|
||||||
|
|
||||||
// Panes
|
// Panes
|
||||||
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
|
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
|
||||||
ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent());
|
ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent());
|
||||||
ko.components.register(
|
|
||||||
"delete-collection-confirmation-pane",
|
|
||||||
new PaneComponents.DeleteCollectionConfirmationPaneComponent()
|
|
||||||
);
|
|
||||||
|
|
||||||
ko.components.register("graph-new-vertex-pane", new PaneComponents.GraphNewVertexPaneComponent());
|
|
||||||
ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent());
|
ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent());
|
||||||
ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent());
|
ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent());
|
||||||
ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent());
|
ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent());
|
||||||
ko.components.register("table-query-select-pane", new PaneComponents.TableQuerySelectPaneComponent());
|
|
||||||
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
|
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
|
||||||
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("github-repos-pane", new PaneComponents.GitHubReposPaneComponent());
|
ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent());
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export class ResourceTreeContextMenuButtonFactory {
|
|||||||
selectedCollection: ViewModels.Collection
|
selectedCollection: ViewModels.Collection
|
||||||
): TreeNodeMenuItem[] {
|
): TreeNodeMenuItem[] {
|
||||||
const items: TreeNodeMenuItem[] = [];
|
const items: TreeNodeMenuItem[] = [];
|
||||||
if (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) {
|
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
||||||
items.push({
|
items.push({
|
||||||
iconSrc: AddSqlQueryIcon,
|
iconSrc: AddSqlQueryIcon,
|
||||||
onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null),
|
onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null),
|
||||||
@@ -80,7 +80,7 @@ export class ResourceTreeContextMenuButtonFactory {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) {
|
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
||||||
items.push({
|
items.push({
|
||||||
iconSrc: AddStoredProcedureIcon,
|
iconSrc: AddStoredProcedureIcon,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
@@ -123,7 +123,7 @@ export class ResourceTreeContextMenuButtonFactory {
|
|||||||
container: Explorer,
|
container: Explorer,
|
||||||
storedProcedure: StoredProcedure
|
storedProcedure: StoredProcedure
|
||||||
): TreeNodeMenuItem[] {
|
): TreeNodeMenuItem[] {
|
||||||
if (container.isPreferredApiCassandra()) {
|
if (userContext.apiType === "Cassandra") {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ export class ResourceTreeContextMenuButtonFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static createTriggerContextMenuItems(container: Explorer, trigger: Trigger): TreeNodeMenuItem[] {
|
public static createTriggerContextMenuItems(container: Explorer, trigger: Trigger): TreeNodeMenuItem[] {
|
||||||
if (container.isPreferredApiCassandra()) {
|
if (userContext.apiType === "Cassandra") {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ export class ResourceTreeContextMenuButtonFactory {
|
|||||||
container: Explorer,
|
container: Explorer,
|
||||||
userDefinedFunction: UserDefinedFunction
|
userDefinedFunction: UserDefinedFunction
|
||||||
): TreeNodeMenuItem[] {
|
): TreeNodeMenuItem[] {
|
||||||
if (container.isPreferredApiCassandra()) {
|
if (userContext.apiType === "Cassandra") {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,3 @@
|
|||||||
import * as React from "react";
|
|
||||||
import { Dialog as FluentDialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric-react/lib/Dialog";
|
|
||||||
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
|
|
||||||
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
|
|
||||||
import { Link } from "office-ui-fabric-react/lib/Link";
|
|
||||||
import {
|
import {
|
||||||
ChoiceGroup,
|
ChoiceGroup,
|
||||||
FontIcon,
|
FontIcon,
|
||||||
@@ -10,6 +5,11 @@ import {
|
|||||||
IProgressIndicatorProps,
|
IProgressIndicatorProps,
|
||||||
ProgressIndicator,
|
ProgressIndicator,
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
|
import { DefaultButton, IButtonProps, PrimaryButton } from "office-ui-fabric-react/lib/Button";
|
||||||
|
import { Dialog as FluentDialog, DialogFooter, DialogType, IDialogProps } from "office-ui-fabric-react/lib/Dialog";
|
||||||
|
import { Link } from "office-ui-fabric-react/lib/Link";
|
||||||
|
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
|
||||||
|
import React, { FunctionComponent } from "react";
|
||||||
|
|
||||||
export interface TextFieldProps extends ITextFieldProps {
|
export interface TextFieldProps extends ITextFieldProps {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -50,61 +50,69 @@ const DIALOG_TITLE_FONT_SIZE = "17px";
|
|||||||
const DIALOG_TITLE_FONT_WEIGHT = 400;
|
const DIALOG_TITLE_FONT_WEIGHT = 400;
|
||||||
const DIALOG_SUBTEXT_FONT_SIZE = "15px";
|
const DIALOG_SUBTEXT_FONT_SIZE = "15px";
|
||||||
|
|
||||||
export class Dialog extends React.Component<DialogProps> {
|
export const Dialog: FunctionComponent<DialogProps> = ({
|
||||||
constructor(props: DialogProps) {
|
title,
|
||||||
super(props);
|
subText,
|
||||||
}
|
isModal,
|
||||||
|
visible,
|
||||||
public render(): JSX.Element {
|
choiceGroupProps,
|
||||||
const dialogProps: IDialogProps = {
|
textFieldProps,
|
||||||
hidden: !this.props.visible,
|
linkProps,
|
||||||
dialogContentProps: {
|
progressIndicatorProps,
|
||||||
type: this.props.type || DialogType.normal,
|
primaryButtonText,
|
||||||
title: this.props.title,
|
secondaryButtonText,
|
||||||
subText: this.props.subText,
|
onPrimaryButtonClick,
|
||||||
styles: {
|
onSecondaryButtonClick,
|
||||||
title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT },
|
primaryButtonDisabled,
|
||||||
subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE },
|
type,
|
||||||
},
|
showCloseButton,
|
||||||
showCloseButton: this.props.showCloseButton || false,
|
onDismiss,
|
||||||
onDismiss: this.props.onDismiss,
|
}: DialogProps) => {
|
||||||
|
const dialogProps: IDialogProps = {
|
||||||
|
hidden: !visible,
|
||||||
|
dialogContentProps: {
|
||||||
|
type: type || DialogType.normal,
|
||||||
|
title,
|
||||||
|
subText,
|
||||||
|
styles: {
|
||||||
|
title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT },
|
||||||
|
subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE },
|
||||||
},
|
},
|
||||||
modalProps: { isBlocking: this.props.isModal, isDarkOverlay: false },
|
showCloseButton: showCloseButton || false,
|
||||||
minWidth: DIALOG_MIN_WIDTH,
|
onDismiss,
|
||||||
maxWidth: DIALOG_MAX_WIDTH,
|
},
|
||||||
};
|
modalProps: { isBlocking: isModal, isDarkOverlay: false },
|
||||||
const choiceGroupProps: IChoiceGroupProps = this.props.choiceGroupProps;
|
minWidth: DIALOG_MIN_WIDTH,
|
||||||
const textFieldProps: ITextFieldProps = this.props.textFieldProps;
|
maxWidth: DIALOG_MAX_WIDTH,
|
||||||
const linkProps: LinkProps = this.props.linkProps;
|
};
|
||||||
const progressIndicatorProps: IProgressIndicatorProps = this.props.progressIndicatorProps;
|
|
||||||
const primaryButtonProps: IButtonProps = {
|
|
||||||
text: this.props.primaryButtonText,
|
|
||||||
disabled: this.props.primaryButtonDisabled || false,
|
|
||||||
onClick: this.props.onPrimaryButtonClick,
|
|
||||||
};
|
|
||||||
const secondaryButtonProps: IButtonProps =
|
|
||||||
this.props.secondaryButtonText && this.props.onSecondaryButtonClick
|
|
||||||
? {
|
|
||||||
text: this.props.secondaryButtonText,
|
|
||||||
onClick: this.props.onSecondaryButtonClick,
|
|
||||||
}
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return (
|
const primaryButtonProps: IButtonProps = {
|
||||||
<FluentDialog {...dialogProps}>
|
text: primaryButtonText,
|
||||||
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
|
disabled: primaryButtonDisabled || false,
|
||||||
{textFieldProps && <TextField {...textFieldProps} />}
|
onClick: onPrimaryButtonClick,
|
||||||
{linkProps && (
|
};
|
||||||
<Link href={linkProps.linkUrl} target="_blank">
|
const secondaryButtonProps: IButtonProps =
|
||||||
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
|
secondaryButtonText && onSecondaryButtonClick
|
||||||
</Link>
|
? {
|
||||||
)}
|
text: secondaryButtonText,
|
||||||
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
|
onClick: onSecondaryButtonClick,
|
||||||
<DialogFooter>
|
}
|
||||||
<PrimaryButton {...primaryButtonProps} />
|
: undefined;
|
||||||
{secondaryButtonProps && <DefaultButton {...secondaryButtonProps} />}
|
|
||||||
</DialogFooter>
|
return (
|
||||||
</FluentDialog>
|
<FluentDialog {...dialogProps}>
|
||||||
);
|
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
|
||||||
}
|
{textFieldProps && <TextField {...textFieldProps} />}
|
||||||
}
|
{linkProps && (
|
||||||
|
<Link href={linkProps.linkUrl} target="_blank">
|
||||||
|
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
{progressIndicatorProps && <ProgressIndicator {...progressIndicatorProps} />}
|
||||||
|
<DialogFooter>
|
||||||
|
<PrimaryButton {...primaryButtonProps} />
|
||||||
|
{secondaryButtonProps && <DefaultButton {...secondaryButtonProps} />}
|
||||||
|
</DialogFooter>
|
||||||
|
</FluentDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -21,18 +21,18 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { HttpStatusCodes } from "../../../Common/Constants";
|
||||||
|
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
import { IGalleryItem, IJunoResponse, IPublicGalleryData, JunoClient } from "../../../Juno/JunoClient";
|
import { IGalleryItem, IJunoResponse, IPublicGalleryData, JunoClient } from "../../../Juno/JunoClient";
|
||||||
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import { trace } from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
import * as GalleryUtils from "../../../Utils/GalleryUtils";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
import { Dialog, DialogProps } from "../Dialog";
|
import { Dialog, DialogProps } from "../Dialog";
|
||||||
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
|
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
|
||||||
import "./GalleryViewerComponent.less";
|
|
||||||
import { HttpStatusCodes } from "../../../Common/Constants";
|
|
||||||
import Explorer from "../../Explorer";
|
|
||||||
import { CodeOfConductComponent } from "./CodeOfConductComponent";
|
import { CodeOfConductComponent } from "./CodeOfConductComponent";
|
||||||
|
import "./GalleryViewerComponent.less";
|
||||||
import { InfoComponent } from "./InfoComponent/InfoComponent";
|
import { InfoComponent } from "./InfoComponent/InfoComponent";
|
||||||
import { handleError } from "../../../Common/ErrorHandlingUtils";
|
|
||||||
import { trace } from "../../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
|
|
||||||
export interface GalleryViewerComponentProps {
|
export interface GalleryViewerComponentProps {
|
||||||
container?: Explorer;
|
container?: Explorer;
|
||||||
@@ -138,11 +138,11 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
key: SortBy.MostRecent,
|
key: SortBy.MostRecent,
|
||||||
text: GalleryViewerComponent.mostRecentText,
|
text: GalleryViewerComponent.mostRecentText,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: SortBy.MostFavorited,
|
||||||
|
text: GalleryViewerComponent.mostFavoritedText,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
this.sortingOptions.push({
|
|
||||||
key: SortBy.MostFavorited,
|
|
||||||
text: GalleryViewerComponent.mostFavoritedText,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.loadTabContent(this.state.selectedTab, this.state.searchText, this.state.sortBy, false);
|
this.loadTabContent(this.state.selectedTab, this.state.searchText, this.state.sortBy, false);
|
||||||
this.loadFavoriteNotebooks(this.state.searchText, this.state.sortBy, false); // Need this to show correct favorite button state
|
this.loadFavoriteNotebooks(this.state.searchText, this.state.sortBy, false); // Need this to show correct favorite button state
|
||||||
@@ -654,7 +654,8 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
|||||||
};
|
};
|
||||||
|
|
||||||
private onRenderCell = (data?: IGalleryItem): JSX.Element => {
|
private onRenderCell = (data?: IGalleryItem): JSX.Element => {
|
||||||
const isFavorite = this.favoriteNotebooks?.find((item) => item.id === data.id) !== undefined;
|
const isFavorite =
|
||||||
|
this.props.container && this.favoriteNotebooks?.find((item) => item.id === data.id) !== undefined;
|
||||||
const props: GalleryCardComponentProps = {
|
const props: GalleryCardComponentProps = {
|
||||||
data,
|
data,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
|
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
||||||
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import { MongoDBCollectionResource } from "../../../Utils/arm/generatedClients/2020-04-01/types";
|
import { updateUserContext } from "../../../UserContext";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { CollectionSettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
import { CollectionSettingsTabV2 } from "../../Tabs/SettingsTabV2";
|
||||||
import { SettingsComponent, SettingsComponentProps, SettingsComponentState } from "./SettingsComponent";
|
import { SettingsComponent, SettingsComponentProps, SettingsComponentState } from "./SettingsComponent";
|
||||||
@@ -23,13 +23,8 @@ jest.mock("../../../Common/dataAccess/updateCollection", () => ({
|
|||||||
changeFeedPolicy: undefined,
|
changeFeedPolicy: undefined,
|
||||||
analyticalStorageTtl: undefined,
|
analyticalStorageTtl: undefined,
|
||||||
geospatialConfig: undefined,
|
geospatialConfig: undefined,
|
||||||
} as DataModels.Collection),
|
|
||||||
updateMongoDBCollectionThroughRP: jest.fn().mockReturnValue({
|
|
||||||
id: undefined,
|
|
||||||
shardKey: undefined,
|
|
||||||
indexes: [],
|
indexes: [],
|
||||||
analyticalStorageTtl: undefined,
|
}),
|
||||||
} as MongoDBCollectionResource),
|
|
||||||
}));
|
}));
|
||||||
jest.mock("../../../Common/dataAccess/updateOffer", () => ({
|
jest.mock("../../../Common/dataAccess/updateOffer", () => ({
|
||||||
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer),
|
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer),
|
||||||
@@ -44,7 +39,6 @@ describe("SettingsComponent", () => {
|
|||||||
tabPath: "",
|
tabPath: "",
|
||||||
node: undefined,
|
node: undefined,
|
||||||
hashLocation: "settings",
|
hashLocation: "settings",
|
||||||
isActive: ko.observable(false),
|
|
||||||
onUpdateTabsButtons: undefined,
|
onUpdateTabsButtons: undefined,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@@ -113,7 +107,13 @@ describe("SettingsComponent", () => {
|
|||||||
expect(settingsComponentInstance.shouldShowKeyspaceSharedThroughputMessage()).toEqual(false);
|
expect(settingsComponentInstance.shouldShowKeyspaceSharedThroughputMessage()).toEqual(false);
|
||||||
|
|
||||||
const newContainer = new Explorer();
|
const newContainer = new Explorer();
|
||||||
newContainer.isPreferredApiCassandra = ko.computed(() => true);
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableCassandra" }],
|
||||||
|
},
|
||||||
|
} as DataModels.DatabaseAccount,
|
||||||
|
});
|
||||||
|
|
||||||
const newCollection = { ...collection };
|
const newCollection = { ...collection };
|
||||||
newCollection.container = newContainer;
|
newCollection.container = newContainer;
|
||||||
@@ -193,7 +193,6 @@ describe("SettingsComponent", () => {
|
|||||||
};
|
};
|
||||||
await settingsComponentInstance.onSaveClick();
|
await settingsComponentInstance.onSaveClick();
|
||||||
expect(updateCollection).toBeCalled();
|
expect(updateCollection).toBeCalled();
|
||||||
expect(updateMongoDBCollectionThroughRP).toBeCalled();
|
|
||||||
expect(updateOffer).toBeCalled();
|
expect(updateOffer).toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { AuthType } from "../../../AuthType";
|
|||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
|
import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
|
||||||
import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection";
|
import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection";
|
||||||
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
|
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
||||||
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||||
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
@@ -137,7 +137,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.offer = this.collection?.offer();
|
this.offer = this.collection?.offer();
|
||||||
this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl();
|
this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl();
|
||||||
this.shouldShowIndexingPolicyEditor =
|
this.shouldShowIndexingPolicyEditor =
|
||||||
this.container && !this.container.isPreferredApiCassandra() && !this.container.isPreferredApiMongoDB();
|
this.container && userContext.apiType !== "Cassandra" && !this.container.isPreferredApiMongoDB();
|
||||||
|
|
||||||
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
|
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
|
||||||
|
|
||||||
@@ -299,7 +299,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.state.wasAutopilotOriginallySet !== this.state.isAutoPilotSelected;
|
this.state.wasAutopilotOriginallySet !== this.state.isAutoPilotSelected;
|
||||||
|
|
||||||
public shouldShowKeyspaceSharedThroughputMessage = (): boolean =>
|
public shouldShowKeyspaceSharedThroughputMessage = (): boolean =>
|
||||||
this.container && this.container.isPreferredApiCassandra() && hasDatabaseSharedThroughput(this.collection);
|
this.container && userContext.apiType === "Cassandra" && hasDatabaseSharedThroughput(this.collection);
|
||||||
|
|
||||||
public hasConflictResolution = (): boolean =>
|
public hasConflictResolution = (): boolean =>
|
||||||
this.container?.databaseAccount &&
|
this.container?.databaseAccount &&
|
||||||
@@ -782,12 +782,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
if (this.state.isMongoIndexingPolicySaveable && this.mongoDBCollectionResource) {
|
if (this.state.isMongoIndexingPolicySaveable && this.mongoDBCollectionResource) {
|
||||||
try {
|
try {
|
||||||
const newMongoIndexes = this.getMongoIndexesToSave();
|
const newMongoIndexes = this.getMongoIndexesToSave();
|
||||||
const newMongoCollection: MongoDBCollectionResource = {
|
const newMongoCollection = {
|
||||||
...this.mongoDBCollectionResource,
|
...this.mongoDBCollectionResource,
|
||||||
indexes: newMongoIndexes,
|
indexes: newMongoIndexes,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.mongoDBCollectionResource = await updateMongoDBCollectionThroughRP(
|
this.mongoDBCollectionResource = await updateCollection(
|
||||||
this.collection.databaseId,
|
this.collection.databaseId,
|
||||||
this.collection.id(),
|
this.collection.id(),
|
||||||
newMongoCollection
|
newMongoCollection
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { SubSettingsComponent, SubSettingsComponentProps } from "./SubSettingsComponent";
|
import { DatabaseAccount } from "../../../../Contracts/DataModels";
|
||||||
import { container, collection } from "../TestUtils";
|
import { updateUserContext } from "../../../../UserContext";
|
||||||
import { TtlType, GeospatialConfigType, ChangeFeedPolicyState, TtlOnNoDefault, TtlOn, TtlOff } from "../SettingsUtils";
|
|
||||||
import ko from "knockout";
|
|
||||||
import Explorer from "../../../Explorer";
|
import Explorer from "../../../Explorer";
|
||||||
|
import { ChangeFeedPolicyState, GeospatialConfigType, TtlOff, TtlOn, TtlOnNoDefault, TtlType } from "../SettingsUtils";
|
||||||
|
import { collection, container } from "../TestUtils";
|
||||||
|
import { SubSettingsComponent, SubSettingsComponentProps } from "./SubSettingsComponent";
|
||||||
|
|
||||||
describe("SubSettingsComponent", () => {
|
describe("SubSettingsComponent", () => {
|
||||||
container.isPreferredApiDocumentDB = ko.computed(() => true);
|
|
||||||
|
|
||||||
const baseProps: SubSettingsComponentProps = {
|
const baseProps: SubSettingsComponentProps = {
|
||||||
collection: collection,
|
collection: collection,
|
||||||
container: container,
|
container: container,
|
||||||
@@ -106,8 +105,13 @@ describe("SubSettingsComponent", () => {
|
|||||||
|
|
||||||
it("partitionKey not visible", () => {
|
it("partitionKey not visible", () => {
|
||||||
const newContainer = new Explorer();
|
const newContainer = new Explorer();
|
||||||
|
updateUserContext({
|
||||||
newContainer.isPreferredApiCassandra = ko.computed(() => true);
|
databaseAccount: {
|
||||||
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableCassandra" }],
|
||||||
|
},
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
const props = { ...baseProps, container: newContainer };
|
const props = { ...baseProps, container: newContainer };
|
||||||
const subSettingsComponent = new SubSettingsComponent(props);
|
const subSettingsComponent = new SubSettingsComponent(props);
|
||||||
expect(subSettingsComponent.getPartitionKeyVisible()).toEqual(false);
|
expect(subSettingsComponent.getPartitionKeyVisible()).toEqual(false);
|
||||||
|
|||||||
@@ -1,28 +1,38 @@
|
|||||||
|
import {
|
||||||
|
ChoiceGroup,
|
||||||
|
IChoiceGroupOption,
|
||||||
|
Label,
|
||||||
|
Link,
|
||||||
|
MessageBar,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
TextField,
|
||||||
|
} from "office-ui-fabric-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ViewModels from "../../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../../Contracts/ViewModels";
|
||||||
import {
|
import { userContext } from "../../../../UserContext";
|
||||||
GeospatialConfigType,
|
|
||||||
TtlType,
|
|
||||||
ChangeFeedPolicyState,
|
|
||||||
isDirty,
|
|
||||||
IsComponentDirtyResult,
|
|
||||||
TtlOn,
|
|
||||||
TtlOff,
|
|
||||||
TtlOnNoDefault,
|
|
||||||
getSanitizedInputValue,
|
|
||||||
} from "../SettingsUtils";
|
|
||||||
import Explorer from "../../../Explorer";
|
import Explorer from "../../../Explorer";
|
||||||
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
||||||
import { Label, Text, TextField, Stack, IChoiceGroupOption, ChoiceGroup, MessageBar } from "office-ui-fabric-react";
|
|
||||||
import {
|
import {
|
||||||
getTextFieldStyles,
|
|
||||||
changeFeedPolicyToolTip,
|
changeFeedPolicyToolTip,
|
||||||
|
getChoiceGroupStyles,
|
||||||
|
getTextFieldStyles,
|
||||||
|
messageBarStyles,
|
||||||
subComponentStackProps,
|
subComponentStackProps,
|
||||||
titleAndInputStackProps,
|
titleAndInputStackProps,
|
||||||
getChoiceGroupStyles,
|
|
||||||
ttlWarning,
|
ttlWarning,
|
||||||
messageBarStyles,
|
|
||||||
} from "../SettingsRenderUtils";
|
} from "../SettingsRenderUtils";
|
||||||
|
import {
|
||||||
|
ChangeFeedPolicyState,
|
||||||
|
GeospatialConfigType,
|
||||||
|
getSanitizedInputValue,
|
||||||
|
IsComponentDirtyResult,
|
||||||
|
isDirty,
|
||||||
|
TtlOff,
|
||||||
|
TtlOn,
|
||||||
|
TtlOnNoDefault,
|
||||||
|
TtlType,
|
||||||
|
} from "../SettingsUtils";
|
||||||
import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
|
import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
|
||||||
|
|
||||||
export interface SubSettingsComponentProps {
|
export interface SubSettingsComponentProps {
|
||||||
@@ -60,17 +70,15 @@ export interface SubSettingsComponentProps {
|
|||||||
|
|
||||||
export class SubSettingsComponent extends React.Component<SubSettingsComponentProps> {
|
export class SubSettingsComponent extends React.Component<SubSettingsComponentProps> {
|
||||||
private shouldCheckComponentIsDirty = true;
|
private shouldCheckComponentIsDirty = true;
|
||||||
private ttlVisible: boolean;
|
|
||||||
private geospatialVisible: boolean;
|
private geospatialVisible: boolean;
|
||||||
private partitionKeyValue: string;
|
private partitionKeyValue: string;
|
||||||
private partitionKeyName: string;
|
private partitionKeyName: string;
|
||||||
|
|
||||||
constructor(props: SubSettingsComponentProps) {
|
constructor(props: SubSettingsComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.ttlVisible = (this.props.container && !this.props.container.isPreferredApiCassandra()) || false;
|
this.geospatialVisible = userContext.apiType === "SQL";
|
||||||
this.geospatialVisible = this.props.container.isPreferredApiDocumentDB();
|
|
||||||
this.partitionKeyValue = "/" + this.props.collection.partitionKeyProperty;
|
this.partitionKeyValue = "/" + this.props.collection.partitionKeyProperty;
|
||||||
this.partitionKeyName = this.props.container.isPreferredApiMongoDB() ? "Shard key" : "Partition key";
|
this.partitionKeyName = userContext.apiType === "Mongo" ? "Shard key" : "Partition key";
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
@@ -170,39 +178,51 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
): void =>
|
): void =>
|
||||||
this.props.onChangeFeedPolicyChange(ChangeFeedPolicyState[option.key as keyof typeof ChangeFeedPolicyState]);
|
this.props.onChangeFeedPolicyChange(ChangeFeedPolicyState[option.key as keyof typeof ChangeFeedPolicyState]);
|
||||||
|
|
||||||
private getTtlComponent = (): JSX.Element => (
|
private getTtlComponent = (): JSX.Element =>
|
||||||
<Stack {...titleAndInputStackProps}>
|
userContext.apiType === "Mongo" ? (
|
||||||
<ChoiceGroup
|
<MessageBar
|
||||||
id="timeToLive"
|
messageBarIconProps={{ iconName: "InfoSolid", className: "messageBarInfoIcon" }}
|
||||||
label="Time to Live"
|
styles={{ text: { fontSize: 14 } }}
|
||||||
selectedKey={this.props.timeToLive}
|
>
|
||||||
options={this.ttlChoiceGroupOptions}
|
To enable time-to-live (TTL) for your collection/documents,
|
||||||
onChange={this.onTtlChange}
|
<Link href="https://docs.microsoft.com/en-us/azure/cosmos-db/mongodb-time-to-live" target="_blank">
|
||||||
styles={getChoiceGroupStyles(this.props.timeToLive, this.props.timeToLiveBaseline)}
|
create a TTL index
|
||||||
/>
|
</Link>
|
||||||
{isDirty(this.props.timeToLive, this.props.timeToLiveBaseline) && this.props.timeToLive === TtlType.On && (
|
.
|
||||||
<MessageBar
|
</MessageBar>
|
||||||
messageBarIconProps={{ iconName: "InfoSolid", className: "messageBarInfoIcon" }}
|
) : (
|
||||||
styles={messageBarStyles}
|
<Stack {...titleAndInputStackProps}>
|
||||||
>
|
<ChoiceGroup
|
||||||
{ttlWarning}
|
id="timeToLive"
|
||||||
</MessageBar>
|
label="Time to Live"
|
||||||
)}
|
selectedKey={this.props.timeToLive}
|
||||||
{this.props.timeToLive === TtlType.On && (
|
options={this.ttlChoiceGroupOptions}
|
||||||
<TextField
|
onChange={this.onTtlChange}
|
||||||
id="timeToLiveSeconds"
|
styles={getChoiceGroupStyles(this.props.timeToLive, this.props.timeToLiveBaseline)}
|
||||||
styles={getTextFieldStyles(this.props.timeToLiveSeconds, this.props.timeToLiveSecondsBaseline)}
|
|
||||||
type="number"
|
|
||||||
required
|
|
||||||
min={1}
|
|
||||||
max={Int32.Max}
|
|
||||||
value={this.props.timeToLiveSeconds?.toString()}
|
|
||||||
onChange={this.onTimeToLiveSecondsChange}
|
|
||||||
suffix="second(s)"
|
|
||||||
/>
|
/>
|
||||||
)}
|
{isDirty(this.props.timeToLive, this.props.timeToLiveBaseline) && this.props.timeToLive === TtlType.On && (
|
||||||
</Stack>
|
<MessageBar
|
||||||
);
|
messageBarIconProps={{ iconName: "InfoSolid", className: "messageBarInfoIcon" }}
|
||||||
|
styles={messageBarStyles}
|
||||||
|
>
|
||||||
|
{ttlWarning}
|
||||||
|
</MessageBar>
|
||||||
|
)}
|
||||||
|
{this.props.timeToLive === TtlType.On && (
|
||||||
|
<TextField
|
||||||
|
id="timeToLiveSeconds"
|
||||||
|
styles={getTextFieldStyles(this.props.timeToLiveSeconds, this.props.timeToLiveSecondsBaseline)}
|
||||||
|
type="number"
|
||||||
|
required
|
||||||
|
min={1}
|
||||||
|
max={Int32.Max}
|
||||||
|
value={this.props.timeToLiveSeconds?.toString()}
|
||||||
|
onChange={this.onTimeToLiveSecondsChange}
|
||||||
|
suffix="second(s)"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
|
||||||
private analyticalTtlChoiceGroupOptions: IChoiceGroupOption[] = [
|
private analyticalTtlChoiceGroupOptions: IChoiceGroupOption[] = [
|
||||||
{ key: TtlType.Off, text: "Off", disabled: true },
|
{ key: TtlType.Off, text: "Off", disabled: true },
|
||||||
@@ -300,8 +320,8 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
|
|
||||||
public getPartitionKeyVisible = (): boolean => {
|
public getPartitionKeyVisible = (): boolean => {
|
||||||
if (
|
if (
|
||||||
this.props.container.isPreferredApiCassandra() ||
|
userContext.apiType === "Cassandra" ||
|
||||||
this.props.container.isPreferredApiTable() ||
|
userContext.apiType === "Tables" ||
|
||||||
!this.props.collection.partitionKeyProperty ||
|
!this.props.collection.partitionKeyProperty ||
|
||||||
(this.props.container.isPreferredApiMongoDB() && this.props.collection.partitionKey.systemKey)
|
(this.props.container.isPreferredApiMongoDB() && this.props.collection.partitionKey.systemKey)
|
||||||
) {
|
) {
|
||||||
@@ -315,7 +335,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
|
|||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Stack {...subComponentStackProps}>
|
<Stack {...subComponentStackProps}>
|
||||||
{this.ttlVisible && this.getTtlComponent()}
|
{userContext.apiType !== "Cassandra" && this.getTtlComponent()}
|
||||||
|
|
||||||
{this.geospatialVisible && this.getGeoSpatialComponent()}
|
{this.geospatialVisible && this.getGeoSpatialComponent()}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ jest.mock("../../Common/dataAccess/createDocument");
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import Q from "q";
|
import Q from "q";
|
||||||
import { createDocument } from "../../Common/dataAccess/createDocument";
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
|
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { updateUserContext } from "../../UserContext";
|
import { updateUserContext } from "../../UserContext";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
@@ -13,11 +14,7 @@ describe("ContainerSampleGenerator", () => {
|
|||||||
const createExplorerStub = (database: ViewModels.Database): Explorer => {
|
const createExplorerStub = (database: ViewModels.Database): Explorer => {
|
||||||
const explorerStub = {} as Explorer;
|
const explorerStub = {} as Explorer;
|
||||||
explorerStub.databases = ko.observableArray<ViewModels.Database>([database]);
|
explorerStub.databases = ko.observableArray<ViewModels.Database>([database]);
|
||||||
explorerStub.isPreferredApiGraph = ko.computed<boolean>(() => false);
|
|
||||||
explorerStub.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
|
explorerStub.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
|
||||||
explorerStub.isPreferredApiDocumentDB = ko.computed<boolean>(() => false);
|
|
||||||
explorerStub.isPreferredApiTable = ko.computed<boolean>(() => false);
|
|
||||||
explorerStub.isPreferredApiCassandra = ko.computed<boolean>(() => false);
|
|
||||||
explorerStub.canExceedMaximumValue = ko.computed<boolean>(() => false);
|
explorerStub.canExceedMaximumValue = ko.computed<boolean>(() => false);
|
||||||
explorerStub.findDatabaseWithId = () => database;
|
explorerStub.findDatabaseWithId = () => database;
|
||||||
explorerStub.refreshAllDatabases = () => Q.resolve();
|
explorerStub.refreshAllDatabases = () => Q.resolve();
|
||||||
@@ -31,7 +28,7 @@ describe("ContainerSampleGenerator", () => {
|
|||||||
it("should insert documents for sql API account", async () => {
|
it("should insert documents for sql API account", async () => {
|
||||||
const sampleCollectionId = "SampleCollection";
|
const sampleCollectionId = "SampleCollection";
|
||||||
const sampleDatabaseId = "SampleDB";
|
const sampleDatabaseId = "SampleDB";
|
||||||
|
updateUserContext({});
|
||||||
const sampleData = {
|
const sampleData = {
|
||||||
databaseId: sampleDatabaseId,
|
databaseId: sampleDatabaseId,
|
||||||
offerThroughput: 400,
|
offerThroughput: 400,
|
||||||
@@ -66,7 +63,7 @@ describe("ContainerSampleGenerator", () => {
|
|||||||
database.findCollectionWithId = () => collection;
|
database.findCollectionWithId = () => collection;
|
||||||
|
|
||||||
const explorerStub = createExplorerStub(database);
|
const explorerStub = createExplorerStub(database);
|
||||||
explorerStub.isPreferredApiDocumentDB = ko.computed<boolean>(() => true);
|
|
||||||
const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub);
|
const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub);
|
||||||
generator.setData(sampleData);
|
generator.setData(sampleData);
|
||||||
|
|
||||||
@@ -116,7 +113,13 @@ describe("ContainerSampleGenerator", () => {
|
|||||||
collection.databaseId = database.id();
|
collection.databaseId = database.id();
|
||||||
|
|
||||||
const explorerStub = createExplorerStub(database);
|
const explorerStub = createExplorerStub(database);
|
||||||
explorerStub.isPreferredApiGraph = ko.computed<boolean>(() => true);
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableGremlin" }],
|
||||||
|
},
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
|
|
||||||
const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub);
|
const generator = await ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub);
|
||||||
generator.setData(sampleData);
|
generator.setData(sampleData);
|
||||||
@@ -125,31 +128,45 @@ describe("ContainerSampleGenerator", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should not create any sample for Mongo API account", async () => {
|
it("should not create any sample for Mongo API account", async () => {
|
||||||
const experience = "not supported api";
|
const experience = "Sample generation not supported for this API Mongo";
|
||||||
const explorerStub = createExplorerStub(undefined);
|
const explorerStub = createExplorerStub(undefined);
|
||||||
explorerStub.isPreferredApiMongoDB = ko.computed<boolean>(() => true);
|
updateUserContext({
|
||||||
explorerStub.defaultExperience = ko.observable<string>(experience);
|
databaseAccount: {
|
||||||
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableMongo" }],
|
||||||
|
},
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
|
|
||||||
// Rejects with error that contains experience
|
// Rejects with error that contains experience
|
||||||
expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
|
expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not create any sample for Table API account", async () => {
|
it("should not create any sample for Table API account", async () => {
|
||||||
const experience = "not supported api";
|
const experience = "Sample generation not supported for this API Tables";
|
||||||
const explorerStub = createExplorerStub(undefined);
|
const explorerStub = createExplorerStub(undefined);
|
||||||
explorerStub.isPreferredApiTable = ko.computed<boolean>(() => true);
|
updateUserContext({
|
||||||
explorerStub.defaultExperience = ko.observable<string>(experience);
|
databaseAccount: {
|
||||||
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableTable" }],
|
||||||
|
},
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
|
|
||||||
// Rejects with error that contains experience
|
// Rejects with error that contains experience
|
||||||
await expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
|
await expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not create any sample for Cassandra API account", async () => {
|
it("should not create any sample for Cassandra API account", async () => {
|
||||||
const experience = "not supported api";
|
const experience = "Sample generation not supported for this API Cassandra";
|
||||||
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableCassandra" }],
|
||||||
|
},
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
const explorerStub = createExplorerStub(undefined);
|
const explorerStub = createExplorerStub(undefined);
|
||||||
explorerStub.isPreferredApiCassandra = ko.computed<boolean>(() => true);
|
|
||||||
explorerStub.defaultExperience = ko.observable<string>(experience);
|
|
||||||
|
|
||||||
// Rejects with error that contains experience
|
// Rejects with error that contains experience
|
||||||
await expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
|
await expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import * as DataModels from "../../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import GraphTab from ".././Tabs/GraphTab";
|
|
||||||
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import Explorer from "../Explorer";
|
|
||||||
import { createCollection } from "../../Common/dataAccess/createCollection";
|
import { createCollection } from "../../Common/dataAccess/createCollection";
|
||||||
import { createDocument } from "../../Common/dataAccess/createDocument";
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import GraphTab from ".././Tabs/GraphTab";
|
||||||
|
import Explorer from "../Explorer";
|
||||||
|
import { GremlinClient } from "../Graph/GraphExplorerComponent/GremlinClient";
|
||||||
|
|
||||||
interface SampleDataFile extends DataModels.CreateCollectionParams {
|
interface SampleDataFile extends DataModels.CreateCollectionParams {
|
||||||
data: any[];
|
data: any[];
|
||||||
@@ -23,16 +23,16 @@ export class ContainerSampleGenerator {
|
|||||||
public static async createSampleGeneratorAsync(container: 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 (userContext.apiType === "Gremlin") {
|
||||||
dataFileContent = await import(
|
dataFileContent = await import(
|
||||||
/* webpackChunkName: "gremlinSampleJsonData" */ "../../../sampleData/gremlinSampleData.json"
|
/* webpackChunkName: "gremlinSampleJsonData" */ "../../../sampleData/gremlinSampleData.json"
|
||||||
);
|
);
|
||||||
} else if (container.isPreferredApiDocumentDB()) {
|
} else if (userContext.apiType === "SQL") {
|
||||||
dataFileContent = await import(
|
dataFileContent = await import(
|
||||||
/* webpackChunkName: "sqlSampleJsonData" */ "../../../sampleData/sqlSampleData.json"
|
/* webpackChunkName: "sqlSampleJsonData" */ "../../../sampleData/sqlSampleData.json"
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(`Sample generation not supported for this API ${container.defaultExperience()}`);
|
return Promise.reject(`Sample generation not supported for this API ${userContext.apiType}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
generator.setData(dataFileContent);
|
generator.setData(dataFileContent);
|
||||||
@@ -73,7 +73,7 @@ export class ContainerSampleGenerator {
|
|||||||
}
|
}
|
||||||
const promises: Q.Promise<any>[] = [];
|
const promises: Q.Promise<any>[] = [];
|
||||||
|
|
||||||
if (this.container.isPreferredApiGraph()) {
|
if (userContext.apiType === "Gremlin") {
|
||||||
// For Gremlin, all queries are executed sequentially, because some queries might be dependent on other queries
|
// For Gremlin, all queries are executed sequentially, because some queries might be dependent on other queries
|
||||||
// (e.g. adding edge requires vertices to be present)
|
// (e.g. adding edge requires vertices to be present)
|
||||||
const queries: string[] = this.sampleDataFile.data;
|
const queries: string[] = this.sampleDataFile.data;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
import { userContext } from "../../UserContext";
|
||||||
|
import { logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
import { ContainerSampleGenerator } from "./ContainerSampleGenerator";
|
||||||
|
|
||||||
export class DataSamplesUtil {
|
export class DataSamplesUtil {
|
||||||
@@ -20,18 +20,16 @@ export class DataSamplesUtil {
|
|||||||
if (this.hasContainer(databaseName, containerName, this.container.databases())) {
|
if (this.hasContainer(databaseName, containerName, this.container.databases())) {
|
||||||
const msg = `The container ${containerName} in database ${databaseName} already exists. Please delete it and retry.`;
|
const msg = `The container ${containerName} in database ${databaseName} already exists. Please delete it and retry.`;
|
||||||
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
|
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
logConsoleError(msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await generator
|
await generator
|
||||||
.createSampleContainerAsync()
|
.createSampleContainerAsync()
|
||||||
.catch((error) =>
|
.catch((error) => logConsoleError(`Error creating sample container: ${error}`));
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Error creating sample container: ${error}`)
|
|
||||||
);
|
|
||||||
const msg = `The sample ${containerName} in database ${databaseName} has been successfully created.`;
|
const msg = `The sample ${containerName} in database ${databaseName} has been successfully created.`;
|
||||||
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
|
this.container.showOkModalDialog(DataSamplesUtil.DialogTitle, msg);
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
|
logConsoleInfo(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,6 +54,6 @@ export class DataSamplesUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public isSampleContainerCreationSupported(): boolean {
|
public isSampleContainerCreationSupported(): boolean {
|
||||||
return this.container.isPreferredApiDocumentDB() || this.container.isPreferredApiGraph();
|
return userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,40 +36,43 @@ import { decryptJWTToken, getAuthorizationHeader } from "../Utils/AuthorizationU
|
|||||||
import { stringToBlob } from "../Utils/BlobUtils";
|
import { stringToBlob } from "../Utils/BlobUtils";
|
||||||
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
|
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
|
||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||||
|
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
||||||
|
import * as PricingUtils from "../Utils/PricingUtils";
|
||||||
import * as ComponentRegisterer from "./ComponentRegisterer";
|
import * as ComponentRegisterer from "./ComponentRegisterer";
|
||||||
import { ArcadiaWorkspaceItem } from "./Controls/Arcadia/ArcadiaMenuPicker";
|
import { ArcadiaWorkspaceItem } from "./Controls/Arcadia/ArcadiaMenuPicker";
|
||||||
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "./Controls/CommandButton/CommandButtonComponent";
|
||||||
import { DialogProps, TextFieldProps } from "./Controls/Dialog";
|
import { DialogProps, TextFieldProps } from "./Controls/Dialog";
|
||||||
import { GalleryTab } from "./Controls/NotebookGallery/GalleryViewerComponent";
|
import { GalleryTab } from "./Controls/NotebookGallery/GalleryViewerComponent";
|
||||||
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
|
import { CommandBarComponentAdapter } from "./Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { ConsoleData, ConsoleDataType } from "./Menus/NotificationConsole/NotificationConsoleComponent";
|
import { ConsoleData } from "./Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
import * as FileSystemUtil from "./Notebook/FileSystemUtil";
|
import * as FileSystemUtil from "./Notebook/FileSystemUtil";
|
||||||
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
|
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
|
||||||
import { NotebookUtil } from "./Notebook/NotebookUtil";
|
import { NotebookUtil } from "./Notebook/NotebookUtil";
|
||||||
import AddCollectionPane from "./Panes/AddCollectionPane";
|
import AddCollectionPane from "./Panes/AddCollectionPane";
|
||||||
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
|
||||||
import AddDatabasePane from "./Panes/AddDatabasePane";
|
import AddDatabasePane from "./Panes/AddDatabasePane";
|
||||||
import { BrowseQueriesPanel } from "./Panes/BrowseQueriesPanel";
|
import { BrowseQueriesPane } from "./Panes/BrowseQueriesPane/BrowseQueriesPane";
|
||||||
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
|
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
|
||||||
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
|
import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
|
||||||
import DeleteCollectionConfirmationPane from "./Panes/DeleteCollectionConfirmationPane";
|
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
|
||||||
import { DeleteCollectionConfirmationPanel } from "./Panes/DeleteCollectionConfirmationPanel";
|
|
||||||
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
|
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
|
||||||
import { ExecuteSprocParamsPanel } from "./Panes/ExecuteSprocParamsPanel";
|
import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane";
|
||||||
import GraphStylingPane from "./Panes/GraphStylingPane";
|
import GraphStylingPane from "./Panes/GraphStylingPane";
|
||||||
import { LoadQueryPanel } from "./Panes/LoadQueryPanel";
|
import { LoadQueryPane } from "./Panes/LoadQueryPane/LoadQueryPane";
|
||||||
import NewVertexPane from "./Panes/NewVertexPane";
|
import { SaveQueryPane } from "./Panes/SaveQueryPane/SaveQueryPane";
|
||||||
import { SaveQueryPanel } from "./Panes/SaveQueryPanel";
|
import { SettingsPane } from "./Panes/SettingsPane/SettingsPane";
|
||||||
import { SettingsPane } from "./Panes/SettingsPane";
|
import { SetupNoteBooksPanel } from "./Panes/SetupNotebooksPanel/SetupNotebooksPanel";
|
||||||
import { SetupNotebooksPane } from "./Panes/SetupNotebooksPane";
|
|
||||||
import { StringInputPane } from "./Panes/StringInputPane";
|
import { StringInputPane } from "./Panes/StringInputPane";
|
||||||
import AddTableEntityPane from "./Panes/Tables/AddTableEntityPane";
|
import { AddTableEntityPanel } from "./Panes/Tables/AddTableEntityPanel";
|
||||||
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
|
import EditTableEntityPane from "./Panes/Tables/EditTableEntityPane";
|
||||||
import { QuerySelectPane } from "./Panes/Tables/QuerySelectPane";
|
import { TableQuerySelectPanel } from "./Panes/Tables/TableQuerySelectPanel";
|
||||||
import { UploadFilePane } from "./Panes/UploadFilePane";
|
import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane";
|
||||||
import { UploadItemsPane } from "./Panes/UploadItemsPane";
|
import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane";
|
||||||
|
import TableListViewModal from "./Tables/DataTable/TableEntityListViewModel";
|
||||||
|
import QueryViewModel from "./Tables/QueryBuilder/QueryViewModel";
|
||||||
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
|
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
|
||||||
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
|
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
|
||||||
|
import QueryTablesTab from "./Tabs/QueryTablesTab";
|
||||||
import TabsBase from "./Tabs/TabsBase";
|
import TabsBase from "./Tabs/TabsBase";
|
||||||
import { TabsManager } from "./Tabs/TabsManager";
|
import { TabsManager } from "./Tabs/TabsManager";
|
||||||
import TerminalTab from "./Tabs/TerminalTab";
|
import TerminalTab from "./Tabs/TerminalTab";
|
||||||
@@ -78,8 +81,6 @@ import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
|
|||||||
import { ResourceTreeAdapter } from "./Tree/ResourceTreeAdapter";
|
import { ResourceTreeAdapter } from "./Tree/ResourceTreeAdapter";
|
||||||
import { ResourceTreeAdapterForResourceToken } from "./Tree/ResourceTreeAdapterForResourceToken";
|
import { ResourceTreeAdapterForResourceToken } from "./Tree/ResourceTreeAdapterForResourceToken";
|
||||||
import StoredProcedure from "./Tree/StoredProcedure";
|
import StoredProcedure from "./Tree/StoredProcedure";
|
||||||
import Trigger from "./Tree/Trigger";
|
|
||||||
import UserDefinedFunction from "./Tree/UserDefinedFunction";
|
|
||||||
|
|
||||||
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
|
||||||
@@ -117,31 +118,11 @@ export default class Explorer {
|
|||||||
* Use userContext.apiType instead
|
* Use userContext.apiType instead
|
||||||
* */
|
* */
|
||||||
public defaultExperience: ko.Observable<string>;
|
public defaultExperience: ko.Observable<string>;
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* Compare a string with userContext.apiType instead: userContext.apiType === "SQL"
|
|
||||||
* */
|
|
||||||
public isPreferredApiDocumentDB: ko.Computed<boolean>;
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* Compare a string with userContext.apiType instead: userContext.apiType === "Cassandra"
|
|
||||||
* */
|
|
||||||
public isPreferredApiCassandra: ko.Computed<boolean>;
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
* Compare a string with userContext.apiType instead: userContext.apiType === "Mongo"
|
* Compare a string with userContext.apiType instead: userContext.apiType === "Mongo"
|
||||||
* */
|
* */
|
||||||
public isPreferredApiMongoDB: ko.Computed<boolean>;
|
public isPreferredApiMongoDB: ko.Computed<boolean>;
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* Compare a string with userContext.apiType instead: userContext.apiType === "Gremlin"
|
|
||||||
* */
|
|
||||||
public isPreferredApiGraph: ko.Computed<boolean>;
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* Compare a string with userContext.apiType instead: userContext.apiType === "Tables"
|
|
||||||
* */
|
|
||||||
public isPreferredApiTable: ko.Computed<boolean>;
|
|
||||||
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
|
public isFixedCollectionWithSharedThroughputSupported: ko.Computed<boolean>;
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
@@ -191,23 +172,16 @@ export default class Explorer {
|
|||||||
// Contextual panes
|
// Contextual panes
|
||||||
public addDatabasePane: AddDatabasePane;
|
public addDatabasePane: AddDatabasePane;
|
||||||
public addCollectionPane: AddCollectionPane;
|
public addCollectionPane: AddCollectionPane;
|
||||||
public deleteCollectionConfirmationPane: DeleteCollectionConfirmationPane;
|
|
||||||
public graphStylingPane: GraphStylingPane;
|
public graphStylingPane: GraphStylingPane;
|
||||||
public addTableEntityPane: AddTableEntityPane;
|
|
||||||
public editTableEntityPane: EditTableEntityPane;
|
public editTableEntityPane: EditTableEntityPane;
|
||||||
public querySelectPane: QuerySelectPane;
|
|
||||||
public newVertexPane: NewVertexPane;
|
|
||||||
public cassandraAddCollectionPane: CassandraAddCollectionPane;
|
public cassandraAddCollectionPane: CassandraAddCollectionPane;
|
||||||
public stringInputPane: StringInputPane;
|
public stringInputPane: StringInputPane;
|
||||||
public setupNotebooksPane: SetupNotebooksPane;
|
|
||||||
public gitHubReposPane: ContextualPaneBase;
|
public gitHubReposPane: ContextualPaneBase;
|
||||||
public publishNotebookPaneAdapter: ReactAdapter;
|
public publishNotebookPaneAdapter: ReactAdapter;
|
||||||
public copyNotebookPaneAdapter: ReactAdapter;
|
|
||||||
|
|
||||||
// features
|
// features
|
||||||
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
||||||
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
|
||||||
public isCopyNotebookPaneEnabled: ko.Observable<boolean>;
|
|
||||||
public isHostedDataExplorerEnabled: ko.Computed<boolean>;
|
public isHostedDataExplorerEnabled: ko.Computed<boolean>;
|
||||||
public isRightPanelV2Enabled: ko.Computed<boolean>;
|
public isRightPanelV2Enabled: ko.Computed<boolean>;
|
||||||
public isMongoIndexingEnabled: ko.Observable<boolean>;
|
public isMongoIndexingEnabled: ko.Observable<boolean>;
|
||||||
@@ -305,7 +279,6 @@ export default class Explorer {
|
|||||||
((await this._containsDefaultNotebookWorkspace(this.databaseAccount())) ||
|
((await this._containsDefaultNotebookWorkspace(this.databaseAccount())) ||
|
||||||
userContext.features.enableNotebooks)
|
userContext.features.enableNotebooks)
|
||||||
);
|
);
|
||||||
|
|
||||||
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
|
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
|
||||||
isNotebookEnabled: this.isNotebookEnabled(),
|
isNotebookEnabled: this.isNotebookEnabled(),
|
||||||
dataExplorerArea: Constants.Areas.Notebook,
|
dataExplorerArea: Constants.Areas.Notebook,
|
||||||
@@ -358,7 +331,6 @@ export default class Explorer {
|
|||||||
this.isGitHubPaneEnabled = ko.observable<boolean>(false);
|
this.isGitHubPaneEnabled = ko.observable<boolean>(false);
|
||||||
this.isMongoIndexingEnabled = ko.observable<boolean>(false);
|
this.isMongoIndexingEnabled = ko.observable<boolean>(false);
|
||||||
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
|
this.isPublishNotebookPaneEnabled = ko.observable<boolean>(false);
|
||||||
this.isCopyNotebookPaneEnabled = ko.observable<boolean>(false);
|
|
||||||
|
|
||||||
this.canExceedMaximumValue = ko.computed<boolean>(() => userContext.features.canExceedMaximumValue);
|
this.canExceedMaximumValue = ko.computed<boolean>(() => userContext.features.canExceedMaximumValue);
|
||||||
|
|
||||||
@@ -422,25 +394,6 @@ export default class Explorer {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.isPreferredApiDocumentDB = ko.computed(() => {
|
|
||||||
const defaultExperience = (this.defaultExperience && this.defaultExperience()) || "";
|
|
||||||
return defaultExperience.toLowerCase() === Constants.DefaultAccountExperience.DocumentDB.toLowerCase();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.isPreferredApiCassandra = ko.computed(() => {
|
|
||||||
const defaultExperience = (this.defaultExperience && this.defaultExperience()) || "";
|
|
||||||
return defaultExperience.toLowerCase() === Constants.DefaultAccountExperience.Cassandra.toLowerCase();
|
|
||||||
});
|
|
||||||
this.isPreferredApiGraph = ko.computed(() => {
|
|
||||||
const defaultExperience = (this.defaultExperience && this.defaultExperience()) || "";
|
|
||||||
return defaultExperience.toLowerCase() === Constants.DefaultAccountExperience.Graph.toLowerCase();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.isPreferredApiTable = ko.computed(() => {
|
|
||||||
const defaultExperience = (this.defaultExperience && this.defaultExperience()) || "";
|
|
||||||
return defaultExperience.toLowerCase() === Constants.DefaultAccountExperience.Table.toLowerCase();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.isFixedCollectionWithSharedThroughputSupported = ko.computed(() => {
|
this.isFixedCollectionWithSharedThroughputSupported = ko.computed(() => {
|
||||||
if (userContext.features.enableFixedCollectionWithSharedThroughput) {
|
if (userContext.features.enableFixedCollectionWithSharedThroughput) {
|
||||||
return true;
|
return true;
|
||||||
@@ -499,7 +452,9 @@ export default class Explorer {
|
|||||||
|
|
||||||
this.isHostedDataExplorerEnabled = ko.computed<boolean>(
|
this.isHostedDataExplorerEnabled = ko.computed<boolean>(
|
||||||
() =>
|
() =>
|
||||||
configContext.platform === Platform.Portal && !this.isRunningOnNationalCloud() && !this.isPreferredApiGraph()
|
configContext.platform === Platform.Portal &&
|
||||||
|
!this.isRunningOnNationalCloud() &&
|
||||||
|
userContext.apiType !== "Gremlin"
|
||||||
);
|
);
|
||||||
this.isRightPanelV2Enabled = ko.computed<boolean>(() => userContext.features.enableRightPanelV2);
|
this.isRightPanelV2Enabled = ko.computed<boolean>(() => userContext.features.enableRightPanelV2);
|
||||||
this.selectedDatabaseId = ko.computed<string>(() => {
|
this.selectedDatabaseId = ko.computed<string>(() => {
|
||||||
@@ -531,20 +486,13 @@ export default class Explorer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.addCollectionPane = new AddCollectionPane({
|
this.addCollectionPane = new AddCollectionPane({
|
||||||
isPreferredApiTable: ko.computed(() => this.isPreferredApiTable()),
|
isPreferredApiTable: ko.computed(() => userContext.apiType === "Tables"),
|
||||||
id: "addcollectionpane",
|
id: "addcollectionpane",
|
||||||
visible: ko.observable<boolean>(false),
|
visible: ko.observable<boolean>(false),
|
||||||
|
|
||||||
container: this,
|
container: this,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.deleteCollectionConfirmationPane = new DeleteCollectionConfirmationPane({
|
|
||||||
id: "deletecollectionconfirmationpane",
|
|
||||||
visible: ko.observable<boolean>(false),
|
|
||||||
|
|
||||||
container: this,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.graphStylingPane = new GraphStylingPane({
|
this.graphStylingPane = new GraphStylingPane({
|
||||||
id: "graphstylingpane",
|
id: "graphstylingpane",
|
||||||
visible: ko.observable<boolean>(false),
|
visible: ko.observable<boolean>(false),
|
||||||
@@ -552,13 +500,6 @@ export default class Explorer {
|
|||||||
container: this,
|
container: this,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addTableEntityPane = new AddTableEntityPane({
|
|
||||||
id: "addtableentitypane",
|
|
||||||
visible: ko.observable<boolean>(false),
|
|
||||||
|
|
||||||
container: this,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.editTableEntityPane = new EditTableEntityPane({
|
this.editTableEntityPane = new EditTableEntityPane({
|
||||||
id: "edittableentitypane",
|
id: "edittableentitypane",
|
||||||
visible: ko.observable<boolean>(false),
|
visible: ko.observable<boolean>(false),
|
||||||
@@ -566,20 +507,6 @@ export default class Explorer {
|
|||||||
container: this,
|
container: this,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.querySelectPane = new QuerySelectPane({
|
|
||||||
id: "queryselectpane",
|
|
||||||
visible: ko.observable<boolean>(false),
|
|
||||||
|
|
||||||
container: this,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.newVertexPane = new NewVertexPane({
|
|
||||||
id: "newvertexpane",
|
|
||||||
visible: ko.observable<boolean>(false),
|
|
||||||
|
|
||||||
container: this,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.cassandraAddCollectionPane = new CassandraAddCollectionPane({
|
this.cassandraAddCollectionPane = new CassandraAddCollectionPane({
|
||||||
id: "cassandraaddcollectionpane",
|
id: "cassandraaddcollectionpane",
|
||||||
visible: ko.observable<boolean>(false),
|
visible: ko.observable<boolean>(false),
|
||||||
@@ -594,27 +521,21 @@ export default class Explorer {
|
|||||||
container: this,
|
container: this,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setupNotebooksPane = new SetupNotebooksPane({
|
|
||||||
id: "setupnotebookspane",
|
|
||||||
visible: ko.observable<boolean>(false),
|
|
||||||
|
|
||||||
container: this,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.tabsManager = params?.tabsManager ?? new TabsManager();
|
this.tabsManager = params?.tabsManager ?? new TabsManager();
|
||||||
|
this.tabsManager.openedTabs.subscribe((tabs) => {
|
||||||
|
if (tabs.length === 0) {
|
||||||
|
this.selectedNode(undefined);
|
||||||
|
this.onUpdateTabsButtons([]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this._panes = [
|
this._panes = [
|
||||||
this.addDatabasePane,
|
this.addDatabasePane,
|
||||||
this.addCollectionPane,
|
this.addCollectionPane,
|
||||||
this.deleteCollectionConfirmationPane,
|
|
||||||
this.graphStylingPane,
|
this.graphStylingPane,
|
||||||
this.addTableEntityPane,
|
|
||||||
this.editTableEntityPane,
|
this.editTableEntityPane,
|
||||||
this.querySelectPane,
|
|
||||||
this.newVertexPane,
|
|
||||||
this.cassandraAddCollectionPane,
|
this.cassandraAddCollectionPane,
|
||||||
this.stringInputPane,
|
this.stringInputPane,
|
||||||
this.setupNotebooksPane,
|
|
||||||
];
|
];
|
||||||
this.addDatabaseText.subscribe((addDatabaseText: string) => this.addDatabasePane.title(addDatabaseText));
|
this.addDatabaseText.subscribe((addDatabaseText: string) => this.addDatabasePane.title(addDatabaseText));
|
||||||
this.isTabsContentExpanded = ko.observable(false);
|
this.isTabsContentExpanded = ko.observable(false);
|
||||||
@@ -644,8 +565,6 @@ export default class Explorer {
|
|||||||
this.addCollectionPane.collectionWithThroughputInSharedTitle(
|
this.addCollectionPane.collectionWithThroughputInSharedTitle(
|
||||||
"Provision dedicated throughput for this container"
|
"Provision dedicated throughput for this container"
|
||||||
);
|
);
|
||||||
this.deleteCollectionConfirmationPane.title("Delete Container");
|
|
||||||
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the container id");
|
|
||||||
this.refreshTreeTitle("Refresh containers");
|
this.refreshTreeTitle("Refresh containers");
|
||||||
break;
|
break;
|
||||||
case "Mongo":
|
case "Mongo":
|
||||||
@@ -672,8 +591,6 @@ export default class Explorer {
|
|||||||
this.addCollectionPane.title("Add Graph");
|
this.addCollectionPane.title("Add Graph");
|
||||||
this.addCollectionPane.collectionIdTitle("Graph id");
|
this.addCollectionPane.collectionIdTitle("Graph id");
|
||||||
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this graph");
|
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this graph");
|
||||||
this.deleteCollectionConfirmationPane.title("Delete Graph");
|
|
||||||
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the graph id");
|
|
||||||
this.refreshTreeTitle("Refresh graphs");
|
this.refreshTreeTitle("Refresh graphs");
|
||||||
break;
|
break;
|
||||||
case "Tables":
|
case "Tables":
|
||||||
@@ -687,10 +604,7 @@ export default class Explorer {
|
|||||||
this.addCollectionPane.collectionIdTitle("Table id");
|
this.addCollectionPane.collectionIdTitle("Table id");
|
||||||
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table");
|
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table");
|
||||||
this.refreshTreeTitle("Refresh tables");
|
this.refreshTreeTitle("Refresh tables");
|
||||||
this.addTableEntityPane.title("Add Table Entity");
|
|
||||||
this.editTableEntityPane.title("Edit Table Entity");
|
this.editTableEntityPane.title("Edit Table Entity");
|
||||||
this.deleteCollectionConfirmationPane.title("Delete Table");
|
|
||||||
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the table id");
|
|
||||||
this.tableDataClient = new TablesAPIDataClient();
|
this.tableDataClient = new TablesAPIDataClient();
|
||||||
break;
|
break;
|
||||||
case "Cassandra":
|
case "Cassandra":
|
||||||
@@ -704,10 +618,7 @@ export default class Explorer {
|
|||||||
this.addCollectionPane.collectionIdTitle("Table id");
|
this.addCollectionPane.collectionIdTitle("Table id");
|
||||||
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table");
|
this.addCollectionPane.collectionWithThroughputInSharedTitle("Provision dedicated throughput for this table");
|
||||||
this.refreshTreeTitle("Refresh tables");
|
this.refreshTreeTitle("Refresh tables");
|
||||||
this.addTableEntityPane.title("Add Table Row");
|
|
||||||
this.editTableEntityPane.title("Edit Table Row");
|
this.editTableEntityPane.title("Edit Table Row");
|
||||||
this.deleteCollectionConfirmationPane.title("Delete Table");
|
|
||||||
this.deleteCollectionConfirmationPane.collectionIdConfirmationText("Confirm by typing the table id");
|
|
||||||
this.tableDataClient = new CassandraAPIDataClient();
|
this.tableDataClient = new CassandraAPIDataClient();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -802,8 +713,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
onPrimaryButtonClick: async () => {
|
onPrimaryButtonClick: async () => {
|
||||||
const startTime = TelemetryProcessor.traceStart(Action.EnableAzureSynapseLink);
|
const startTime = TelemetryProcessor.traceStart(Action.EnableAzureSynapseLink);
|
||||||
const logId = NotificationConsoleUtils.logConsoleMessage(
|
const clearInProgressMessage = logConsoleProgress(
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
"Enabling Azure Synapse Link for this account. This may take a few minutes before you can enable analytical store for this account."
|
"Enabling Azure Synapse Link for this account. This may take a few minutes before you can enable analytical store for this account."
|
||||||
);
|
);
|
||||||
this.isSynapseLinkUpdating(true);
|
this.isSynapseLinkUpdating(true);
|
||||||
@@ -821,19 +731,13 @@ export default class Explorer {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(logId);
|
clearInProgressMessage();
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleInfo("Enabled Azure Synapse Link for this account");
|
||||||
ConsoleDataType.Info,
|
|
||||||
"Enabled Azure Synapse Link for this account"
|
|
||||||
);
|
|
||||||
TelemetryProcessor.traceSuccess(Action.EnableAzureSynapseLink, {}, startTime);
|
TelemetryProcessor.traceSuccess(Action.EnableAzureSynapseLink, {}, startTime);
|
||||||
this.databaseAccount(databaseAccount);
|
this.databaseAccount(databaseAccount);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(logId);
|
clearInProgressMessage();
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleError(`Enabling Azure Synapse Link for this account failed. ${getErrorMessage(error)}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Enabling Azure Synapse Link for this account failed. ${getErrorMessage(error)}`
|
|
||||||
);
|
|
||||||
TelemetryProcessor.traceFailure(Action.EnableAzureSynapseLink, {}, startTime);
|
TelemetryProcessor.traceFailure(Action.EnableAzureSynapseLink, {}, startTime);
|
||||||
} finally {
|
} finally {
|
||||||
this.isSynapseLinkUpdating(false);
|
this.isSynapseLinkUpdating(false);
|
||||||
@@ -960,10 +864,7 @@ export default class Explorer {
|
|||||||
},
|
},
|
||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleError(`Error while refreshing databases: ${errorMessage}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while refreshing databases: ${errorMessage}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1024,7 +925,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
// Facade
|
// Facade
|
||||||
public provideFeedbackEmail = () => {
|
public provideFeedbackEmail = () => {
|
||||||
window.open(Constants.Urls.feedbackEmail, "_self");
|
window.open(Constants.Urls.feedbackEmail, "_blank");
|
||||||
};
|
};
|
||||||
|
|
||||||
public async getArcadiaToken(): Promise<string> {
|
public async getArcadiaToken(): Promise<string> {
|
||||||
@@ -1184,20 +1085,20 @@ export default class Explorer {
|
|||||||
|
|
||||||
private _resetNotebookWorkspace = async () => {
|
private _resetNotebookWorkspace = async () => {
|
||||||
this._closeModalDialog();
|
this._closeModalDialog();
|
||||||
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Resetting notebook workspace");
|
const clearInProgressMessage = logConsoleProgress("Resetting notebook workspace");
|
||||||
try {
|
try {
|
||||||
await this.notebookManager?.notebookClient.resetWorkspace();
|
await this.notebookManager?.notebookClient.resetWorkspace();
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "Successfully reset notebook workspace");
|
logConsoleInfo("Successfully reset notebook workspace");
|
||||||
TelemetryProcessor.traceSuccess(Action.ResetNotebookWorkspace);
|
TelemetryProcessor.traceSuccess(Action.ResetNotebookWorkspace);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to reset notebook workspace: ${error}`);
|
logConsoleError(`Failed to reset notebook workspace: ${error}`);
|
||||||
TelemetryProcessor.traceFailure(Action.ResetNotebookWorkspace, {
|
TelemetryProcessor.traceFailure(Action.ResetNotebookWorkspace, {
|
||||||
error: getErrorMessage(error),
|
error: getErrorMessage(error),
|
||||||
errorStack: getErrorStack(error),
|
errorStack: getErrorStack(error),
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
clearInProgressMessage();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1295,49 +1196,6 @@ export default class Explorer {
|
|||||||
: this.selectedNode().collection) as ViewModels.Collection;
|
: this.selectedNode().collection) as ViewModels.Collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Refactor below methods, minimize dependencies and add unit tests where necessary
|
|
||||||
public findSelectedStoredProcedure(): StoredProcedure {
|
|
||||||
const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
|
|
||||||
return _.find(selectedCollection.storedProcedures(), (storedProcedure: StoredProcedure) => {
|
|
||||||
const openedSprocTab = this.tabsManager.getTabs(
|
|
||||||
ViewModels.CollectionTabKind.StoredProcedures,
|
|
||||||
(tab) => tab.node && tab.node.rid === storedProcedure.rid
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
storedProcedure.rid === this.selectedNode().rid ||
|
|
||||||
(!!openedSprocTab && openedSprocTab.length > 0 && openedSprocTab[0].isActive())
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public findSelectedUDF(): UserDefinedFunction {
|
|
||||||
const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
|
|
||||||
return _.find(selectedCollection.userDefinedFunctions(), (userDefinedFunction: UserDefinedFunction) => {
|
|
||||||
const openedUdfTab = this.tabsManager.getTabs(
|
|
||||||
ViewModels.CollectionTabKind.UserDefinedFunctions,
|
|
||||||
(tab) => tab.node && tab.node.rid === userDefinedFunction.rid
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
userDefinedFunction.rid === this.selectedNode().rid ||
|
|
||||||
(!!openedUdfTab && openedUdfTab.length > 0 && openedUdfTab[0].isActive())
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public findSelectedTrigger(): Trigger {
|
|
||||||
const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
|
|
||||||
return _.find(selectedCollection.triggers(), (trigger: Trigger) => {
|
|
||||||
const openedTriggerTab = this.tabsManager.getTabs(
|
|
||||||
ViewModels.CollectionTabKind.Triggers,
|
|
||||||
(tab) => tab.node && tab.node.rid === trigger.rid
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
trigger.rid === this.selectedNode().rid ||
|
|
||||||
(!!openedTriggerTab && openedTriggerTab.length > 0 && openedTriggerTab[0].isActive())
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public closeAllPanes(): void {
|
public closeAllPanes(): void {
|
||||||
this._panes.forEach((pane: ContextualPaneBase) => pane.close());
|
this._panes.forEach((pane: ContextualPaneBase) => pane.close());
|
||||||
}
|
}
|
||||||
@@ -1575,11 +1433,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public copyNotebook(name: string, content: string): void {
|
public copyNotebook(name: string, content: string): void {
|
||||||
if (this.notebookManager) {
|
this.notebookManager?.openCopyNotebookPane(name, content);
|
||||||
this.notebookManager.openCopyNotebookPane(name, content);
|
|
||||||
this.copyNotebookPaneAdapter = this.notebookManager.copyNotebookPaneAdapter;
|
|
||||||
this.isCopyNotebookPaneEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public showOkModalDialog(title: string, msg: string): void {
|
public showOkModalDialog(title: string, msg: string): void {
|
||||||
@@ -1666,7 +1520,6 @@ export default class Explorer {
|
|||||||
collection: null,
|
collection: null,
|
||||||
masterKey: userContext.masterKey || "",
|
masterKey: userContext.masterKey || "",
|
||||||
hashLocation: "notebooks",
|
hashLocation: "notebooks",
|
||||||
isActive: ko.observable(false),
|
|
||||||
isTabsContentExpanded: ko.observable(true),
|
isTabsContentExpanded: ko.observable(true),
|
||||||
onLoadStartKey: null,
|
onLoadStartKey: null,
|
||||||
onUpdateTabsButtons: this.onUpdateTabsButtons,
|
onUpdateTabsButtons: this.onUpdateTabsButtons,
|
||||||
@@ -1798,11 +1651,7 @@ export default class Explorer {
|
|||||||
clearMessage();
|
clearMessage();
|
||||||
},
|
},
|
||||||
(error: any) => {
|
(error: any) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
logConsoleError(`Could not download notebook ${getErrorMessage(error)}`);
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Could not download notebook ${getErrorMessage(error)}`
|
|
||||||
);
|
|
||||||
|
|
||||||
clearMessage();
|
clearMessage();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -1954,15 +1803,8 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.notebookManager?.notebookContentClient.deleteContentItem(item).then(
|
return this.notebookManager?.notebookContentClient.deleteContentItem(item).then(
|
||||||
() => {
|
() => logConsoleInfo(`Successfully deleted: ${item.path}`),
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Successfully deleted: ${item.path}`);
|
(reason: any) => logConsoleError(`Failed to delete "${item.path}": ${JSON.stringify(reason)}`)
|
||||||
},
|
|
||||||
(reason: any) => {
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Failed to delete "${item.path}": ${JSON.stringify(reason)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1978,11 +1820,7 @@ export default class Explorer {
|
|||||||
|
|
||||||
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
parent = parent || this.resourceTree.myNotebooksContentRoot;
|
||||||
|
|
||||||
const notificationProgressId = NotificationConsoleUtils.logConsoleMessage(
|
const clearInProgressMessage = logConsoleProgress(`Creating new notebook in ${parent.path}`);
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
`Creating new notebook in ${parent.path}`
|
|
||||||
);
|
|
||||||
|
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.CreateNewNotebook, {
|
const startKey: number = TelemetryProcessor.traceStart(Action.CreateNewNotebook, {
|
||||||
dataExplorerArea: Constants.Areas.Notebook,
|
dataExplorerArea: Constants.Areas.Notebook,
|
||||||
});
|
});
|
||||||
@@ -1990,7 +1828,7 @@ export default class Explorer {
|
|||||||
this.notebookManager?.notebookContentClient
|
this.notebookManager?.notebookContentClient
|
||||||
.createNewNotebookFile(parent)
|
.createNewNotebookFile(parent)
|
||||||
.then((newFile: NotebookContentItem) => {
|
.then((newFile: NotebookContentItem) => {
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Successfully created: ${newFile.name}`);
|
logConsoleInfo(`Successfully created: ${newFile.name}`);
|
||||||
TelemetryProcessor.traceSuccess(
|
TelemetryProcessor.traceSuccess(
|
||||||
Action.CreateNewNotebook,
|
Action.CreateNewNotebook,
|
||||||
{
|
{
|
||||||
@@ -2003,7 +1841,7 @@ export default class Explorer {
|
|||||||
.then(() => this.resourceTree.triggerRender())
|
.then(() => this.resourceTree.triggerRender())
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
const errorMessage = `Failed to create a new notebook: ${getErrorMessage(error)}`;
|
const errorMessage = `Failed to create a new notebook: ${getErrorMessage(error)}`;
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, errorMessage);
|
logConsoleError(errorMessage);
|
||||||
TelemetryProcessor.traceFailure(
|
TelemetryProcessor.traceFailure(
|
||||||
Action.CreateNewNotebook,
|
Action.CreateNewNotebook,
|
||||||
{
|
{
|
||||||
@@ -2014,7 +1852,7 @@ export default class Explorer {
|
|||||||
startKey
|
startKey
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(notificationProgressId));
|
.finally(clearInProgressMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public refreshContentItem(item: NotebookContentItem): Promise<void> {
|
public refreshContentItem(item: NotebookContentItem): Promise<void> {
|
||||||
@@ -2072,7 +1910,6 @@ export default class Explorer {
|
|||||||
tabPath: title,
|
tabPath: title,
|
||||||
collection: null,
|
collection: null,
|
||||||
hashLocation: hashLocation,
|
hashLocation: hashLocation,
|
||||||
isActive: ko.observable(false),
|
|
||||||
isTabsContentExpanded: ko.observable(true),
|
isTabsContentExpanded: ko.observable(true),
|
||||||
onLoadStartKey: null,
|
onLoadStartKey: null,
|
||||||
onUpdateTabsButtons: this.onUpdateTabsButtons,
|
onUpdateTabsButtons: this.onUpdateTabsButtons,
|
||||||
@@ -2177,7 +2014,7 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onNewCollectionClicked(): void {
|
public onNewCollectionClicked(): void {
|
||||||
if (this.isPreferredApiCassandra()) {
|
if (userContext.apiType === "Cassandra") {
|
||||||
this.cassandraAddCollectionPane.open();
|
this.cassandraAddCollectionPane.open();
|
||||||
} else if (userContext.features.enableReactPane) {
|
} else if (userContext.features.enableReactPane) {
|
||||||
this.openAddCollectionPanel();
|
this.openAddCollectionPanel();
|
||||||
@@ -2225,7 +2062,7 @@ export default class Explorer {
|
|||||||
const description =
|
const description =
|
||||||
"You have not yet created a notebooks workspace for this account. To proceed and start using notebooks, we'll need to create a default notebooks workspace in this account.";
|
"You have not yet created a notebooks workspace for this account. To proceed and start using notebooks, we'll need to create a default notebooks workspace in this account.";
|
||||||
|
|
||||||
this.setupNotebooksPane.openWithTitleAndDescription(title, description);
|
this.openSetupNotebooksPanel(title, description);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handleOpenFileAction(path: string): Promise<void> {
|
public async handleOpenFileAction(path: string): Promise<void> {
|
||||||
@@ -2287,16 +2124,15 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public openDeleteCollectionConfirmationPane(): void {
|
public openDeleteCollectionConfirmationPane(): void {
|
||||||
userContext.features.enableKOPanel
|
let collectionName = PricingUtils.getCollectionName(userContext.defaultExperience);
|
||||||
? this.deleteCollectionConfirmationPane.open()
|
this.openSidePanel(
|
||||||
: this.openSidePanel(
|
"Delete " + collectionName,
|
||||||
"Delete Collection",
|
<DeleteCollectionConfirmationPane
|
||||||
<DeleteCollectionConfirmationPanel
|
explorer={this}
|
||||||
explorer={this}
|
collectionName={collectionName}
|
||||||
closePanel={() => this.closeSidePanel()}
|
closePanel={this.closeSidePanel}
|
||||||
openNotificationConsole={() => this.expandConsole()}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public openDeleteDatabaseConfirmationPane(): void {
|
public openDeleteDatabaseConfirmationPane(): void {
|
||||||
@@ -2319,10 +2155,14 @@ export default class Explorer {
|
|||||||
this.openSidePanel("Settings", <SettingsPane explorer={this} closePanel={this.closeSidePanel} />);
|
this.openSidePanel("Settings", <SettingsPane explorer={this} closePanel={this.closeSidePanel} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openExecuteSprocParamsPanel(): void {
|
public openExecuteSprocParamsPanel(storedProcedure: StoredProcedure): void {
|
||||||
this.openSidePanel(
|
this.openSidePanel(
|
||||||
"Input parameters",
|
"Input parameters",
|
||||||
<ExecuteSprocParamsPanel explorer={this} closePanel={() => this.closeSidePanel()} />
|
<ExecuteSprocParamsPane
|
||||||
|
explorer={this}
|
||||||
|
storedProcedure={storedProcedure}
|
||||||
|
closePanel={() => this.closeSidePanel()}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2339,15 +2179,15 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public openBrowseQueriesPanel(): void {
|
public openBrowseQueriesPanel(): void {
|
||||||
this.openSidePanel("Open Saved Queries", <BrowseQueriesPanel explorer={this} closePanel={this.closeSidePanel} />);
|
this.openSidePanel("Open Saved Queries", <BrowseQueriesPane explorer={this} closePanel={this.closeSidePanel} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openLoadQueryPanel(): void {
|
public openLoadQueryPanel(): void {
|
||||||
this.openSidePanel("Load Query", <LoadQueryPanel explorer={this} closePanel={() => this.closeSidePanel()} />);
|
this.openSidePanel("Load Query", <LoadQueryPane explorer={this} closePanel={() => this.closeSidePanel()} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openSaveQueryPanel(): void {
|
public openSaveQueryPanel(): void {
|
||||||
this.openSidePanel("Save Query", <SaveQueryPanel explorer={this} closePanel={() => this.closeSidePanel()} />);
|
this.openSidePanel("Save Query", <SaveQueryPane explorer={this} closePanel={() => this.closeSidePanel()} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openUploadFilePanel(parent?: NotebookContentItem): void {
|
public openUploadFilePanel(parent?: NotebookContentItem): void {
|
||||||
@@ -2361,4 +2201,36 @@ export default class Explorer {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public openAddTableEntityPanel(queryTablesTab: QueryTablesTab, tableEntityListViewModel: TableListViewModal): void {
|
||||||
|
this.openSidePanel(
|
||||||
|
"Add Table Entity",
|
||||||
|
<AddTableEntityPanel
|
||||||
|
explorer={this}
|
||||||
|
closePanel={this.closeSidePanel}
|
||||||
|
queryTablesTab={queryTablesTab}
|
||||||
|
tableEntityListViewModel={tableEntityListViewModel}
|
||||||
|
cassandraApiClient={new CassandraAPIDataClient()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public openSetupNotebooksPanel(title: string, description: string): void {
|
||||||
|
this.openSidePanel(
|
||||||
|
title,
|
||||||
|
<SetupNoteBooksPanel
|
||||||
|
explorer={this}
|
||||||
|
closePanel={this.closeSidePanel}
|
||||||
|
openNotificationConsole={() => this.expandConsole()}
|
||||||
|
panelTitle={title}
|
||||||
|
panelDescription={description}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public openTableSelectQueryPanel(queryViewModal: QueryViewModel): void {
|
||||||
|
this.openSidePanel(
|
||||||
|
"Select Column",
|
||||||
|
<TableQuerySelectPanel explorer={this} closePanel={this.closeSidePanel} queryViewModel={queryViewModal} />
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,22 @@
|
|||||||
import * as ko from "knockout";
|
import { BaseType } from "d3";
|
||||||
import Q from "q";
|
|
||||||
import { schemeCategory10 } from "d3-scale-chromatic";
|
|
||||||
import { selectAll, select } from "d3-selection";
|
|
||||||
import { zoom, zoomIdentity } from "d3-zoom";
|
|
||||||
import { scaleOrdinal } from "d3-scale";
|
|
||||||
import { forceSimulation, forceLink, forceCollide, forceManyBody } from "d3-force";
|
|
||||||
import { interpolateNumber, interpolate } from "d3-interpolate";
|
|
||||||
import { map as d3Map } from "d3-collection";
|
import { map as d3Map } from "d3-collection";
|
||||||
import { drag, D3DragEvent } from "d3-drag";
|
import { D3DragEvent, drag } from "d3-drag";
|
||||||
|
import { forceCollide, forceLink, forceManyBody, forceSimulation } from "d3-force";
|
||||||
|
import { interpolate, interpolateNumber } from "d3-interpolate";
|
||||||
|
import { scaleOrdinal } from "d3-scale";
|
||||||
|
import { schemeCategory10 } from "d3-scale-chromatic";
|
||||||
|
import { select, selectAll } from "d3-selection";
|
||||||
|
import { zoom, zoomIdentity } from "d3-zoom";
|
||||||
|
import * as ko from "knockout";
|
||||||
|
import Q from "q";
|
||||||
import _ from "underscore";
|
import _ from "underscore";
|
||||||
import { NeighborType } from "../../../Contracts/ViewModels";
|
|
||||||
import { GraphData, D3Node, D3Link } from "./GraphData";
|
|
||||||
import { HashMap } from "../../../Common/HashMap";
|
|
||||||
import { BaseType } from "d3";
|
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
|
||||||
import { GraphConfig } from "../../Tabs/GraphTab";
|
|
||||||
import { GraphExplorer } from "./GraphExplorer";
|
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
|
import { HashMap } from "../../../Common/HashMap";
|
||||||
|
import { NeighborType } from "../../../Contracts/ViewModels";
|
||||||
|
import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
|
||||||
|
import { GraphConfig } from "../../Tabs/GraphTab";
|
||||||
|
import { D3Link, D3Node, GraphData } from "./GraphData";
|
||||||
|
import { GraphExplorer } from "./GraphExplorer";
|
||||||
|
|
||||||
export interface D3GraphIconMap {
|
export interface D3GraphIconMap {
|
||||||
[key: string]: { data: string; format: string };
|
[key: string]: { data: string; format: string };
|
||||||
@@ -1005,7 +1003,7 @@ export class D3ForceGraph implements GraphRenderer {
|
|||||||
*/
|
*/
|
||||||
private loadNeighbors(v: D3Node, pageAction: PAGE_ACTION) {
|
private loadNeighbors(v: D3Node, pageAction: PAGE_ACTION) {
|
||||||
if (!this.graphDataWrapper.hasVertexId(v.id)) {
|
if (!this.graphDataWrapper.hasVertexId(v.id)) {
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Clicked node not in graph data. id: ${v.id}`);
|
logConsoleError(`Clicked node not in graph data. id: ${v.id}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +1,36 @@
|
|||||||
|
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||||
import * as Q from "q";
|
import * as Q from "q";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as LeftPane from "./LeftPaneComponent";
|
|
||||||
import { MiddlePaneComponent } from "./MiddlePaneComponent";
|
|
||||||
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
|
|
||||||
import * as NodeProperties from "./NodePropertiesComponent";
|
|
||||||
import * as D3ForceGraph from "./D3ForceGraph";
|
|
||||||
import { GraphVizComponentProps } from "./GraphVizComponent";
|
|
||||||
import * as GraphData from "./GraphData";
|
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
|
||||||
import * as GraphUtil from "./GraphUtil";
|
|
||||||
import * as DataModels from "../../../Contracts/DataModels";
|
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
|
||||||
import * as GremlinClient from "./GremlinClient";
|
|
||||||
import * as StorageUtility from "../../../Shared/StorageUtility";
|
|
||||||
import { ArraysByKeyCache } from "./ArraysByKeyCache";
|
|
||||||
import { EdgeInfoCache } from "./EdgeInfoCache";
|
|
||||||
import * as TabComponent from "../../Controls/Tabs/TabComponent";
|
|
||||||
import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility";
|
|
||||||
import { QueryContainerComponent } from "./QueryContainerComponent";
|
|
||||||
import { GraphConfig } from "../../Tabs/GraphTab";
|
|
||||||
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
|
||||||
import LoadGraphIcon from "../../../../images/LoadGraph.png";
|
import LoadGraphIcon from "../../../../images/LoadGraph.png";
|
||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import * as Constants from "../../../Common/Constants";
|
|
||||||
import { InputProperty } from "../../../Contracts/ViewModels";
|
|
||||||
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
|
|
||||||
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
|
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
|
||||||
|
import * as Constants from "../../../Common/Constants";
|
||||||
import { queryDocuments } from "../../../Common/dataAccess/queryDocuments";
|
import { queryDocuments } from "../../../Common/dataAccess/queryDocuments";
|
||||||
import { queryDocumentsPage } from "../../../Common/dataAccess/queryDocumentsPage";
|
import { queryDocumentsPage } from "../../../Common/dataAccess/queryDocumentsPage";
|
||||||
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
|
||||||
import { FeedOptions } from "@azure/cosmos";
|
import * as DataModels from "../../../Contracts/DataModels";
|
||||||
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
|
import { InputProperty } from "../../../Contracts/ViewModels";
|
||||||
|
import * as StorageUtility from "../../../Shared/StorageUtility";
|
||||||
|
import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility";
|
||||||
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
|
||||||
|
import { EditorReact } from "../../Controls/Editor/EditorReact";
|
||||||
|
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
|
||||||
|
import * as TabComponent from "../../Controls/Tabs/TabComponent";
|
||||||
|
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||||
|
import { GraphConfig } from "../../Tabs/GraphTab";
|
||||||
|
import { ArraysByKeyCache } from "./ArraysByKeyCache";
|
||||||
|
import * as D3ForceGraph from "./D3ForceGraph";
|
||||||
|
import { EdgeInfoCache } from "./EdgeInfoCache";
|
||||||
|
import * as GraphData from "./GraphData";
|
||||||
|
import * as GraphUtil from "./GraphUtil";
|
||||||
|
import { GraphVizComponentProps } from "./GraphVizComponent";
|
||||||
|
import * as GremlinClient from "./GremlinClient";
|
||||||
|
import * as LeftPane from "./LeftPaneComponent";
|
||||||
|
import { MiddlePaneComponent } from "./MiddlePaneComponent";
|
||||||
|
import * as NodeProperties from "./NodePropertiesComponent";
|
||||||
|
import { QueryContainerComponent } from "./QueryContainerComponent";
|
||||||
|
|
||||||
export interface GraphAccessor {
|
export interface GraphAccessor {
|
||||||
applyFilter: () => void;
|
applyFilter: () => void;
|
||||||
@@ -697,13 +696,13 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
* @param cmd
|
* @param cmd
|
||||||
*/
|
*/
|
||||||
public submitToBackend(cmd: string): Q.Promise<GremlinClient.GremlinRequestResult> {
|
public submitToBackend(cmd: string): Q.Promise<GremlinClient.GremlinRequestResult> {
|
||||||
const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${cmd}`);
|
const clearConsoleProgress = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${cmd}`);
|
||||||
this.setExecuteCounter(this.executeCounter + 1);
|
this.setExecuteCounter(this.executeCounter + 1);
|
||||||
|
|
||||||
return this.gremlinClient.execute(cmd).then(
|
return this.gremlinClient.execute(cmd).then(
|
||||||
(result: GremlinClient.GremlinRequestResult) => {
|
(result: GremlinClient.GremlinRequestResult) => {
|
||||||
this.setExecuteCounter(this.executeCounter - 1);
|
this.setExecuteCounter(this.executeCounter - 1);
|
||||||
GraphExplorer.clearConsoleProgress(id);
|
clearConsoleProgress();
|
||||||
if (result.isIncomplete) {
|
if (result.isIncomplete) {
|
||||||
const msg = `The query results are too large and only partial results are displayed for: ${cmd}`;
|
const msg = `The query results are too large and only partial results are displayed for: ${cmd}`;
|
||||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, msg);
|
GraphExplorer.reportToConsole(ConsoleDataType.Error, msg);
|
||||||
@@ -718,7 +717,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
(err: string) => {
|
(err: string) => {
|
||||||
this.setExecuteCounter(this.executeCounter - 1);
|
this.setExecuteCounter(this.executeCounter - 1);
|
||||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, `Gremlin query failed: ${cmd}`, err);
|
GraphExplorer.reportToConsole(ConsoleDataType.Error, `Gremlin query failed: ${cmd}`, err);
|
||||||
GraphExplorer.clearConsoleProgress(id);
|
clearConsoleProgress();
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -1083,13 +1082,26 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
* @param errorData additional errors
|
* @param errorData additional errors
|
||||||
* @return id
|
* @return id
|
||||||
*/
|
*/
|
||||||
public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): string {
|
public static reportToConsole(type: ConsoleDataType.InProgress, msg: string, ...errorData: any[]): () => void;
|
||||||
|
public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void;
|
||||||
|
public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void;
|
||||||
|
public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) {
|
||||||
let errorDataStr: string = "";
|
let errorDataStr: string = "";
|
||||||
if (errorData && errorData.length > 0) {
|
if (errorData && errorData.length > 0) {
|
||||||
console.error(msg, errorData);
|
console.error(msg, errorData);
|
||||||
errorDataStr = ": " + JSON.stringify(errorData);
|
errorDataStr = ": " + JSON.stringify(errorData);
|
||||||
}
|
}
|
||||||
return NotificationConsoleUtils.logConsoleMessage(type, `${msg}${errorDataStr}`);
|
|
||||||
|
const consoleMessage = `${msg}${errorDataStr}`;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case ConsoleDataType.Error:
|
||||||
|
return logConsoleError(consoleMessage);
|
||||||
|
case ConsoleDataType.Info:
|
||||||
|
return logConsoleInfo(consoleMessage);
|
||||||
|
case ConsoleDataType.InProgress:
|
||||||
|
return logConsoleProgress(consoleMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setNodePropertiesViewMode(viewMode: NodeProperties.Mode) {
|
private setNodePropertiesViewMode(viewMode: NodeProperties.Mode) {
|
||||||
@@ -1368,7 +1380,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
let { id } = d;
|
let { id } = d;
|
||||||
if (typeof id !== "string") {
|
if (typeof id !== "string") {
|
||||||
const error = `Vertex id is not a string: ${JSON.stringify(id)}.`;
|
const error = `Vertex id is not a string: ${JSON.stringify(id)}.`;
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
|
logConsoleError(error);
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1380,7 +1392,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
pk = pk[0]["_value"];
|
pk = pk[0]["_value"];
|
||||||
} else {
|
} else {
|
||||||
const error = `Vertex pk is not a string nor a non-empty array: ${JSON.stringify(pk)}.`;
|
const error = `Vertex pk is not a string nor a non-empty array: ${JSON.stringify(pk)}.`;
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
|
logConsoleError(error);
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1767,7 +1779,10 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
const queryInfoStr = `${this.currentDocDBQueryInfo.query} (${this.currentDocDBQueryInfo.index + 1}-${
|
const queryInfoStr = `${this.currentDocDBQueryInfo.query} (${this.currentDocDBQueryInfo.index + 1}-${
|
||||||
this.currentDocDBQueryInfo.index + GraphExplorer.ROOT_LIST_PAGE_SIZE
|
this.currentDocDBQueryInfo.index + GraphExplorer.ROOT_LIST_PAGE_SIZE
|
||||||
})`;
|
})`;
|
||||||
const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${queryInfoStr}`);
|
const clearConsoleProgress = GraphExplorer.reportToConsole(
|
||||||
|
ConsoleDataType.InProgress,
|
||||||
|
`Executing: ${queryInfoStr}`
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const results: ViewModels.QueryResults = await queryDocumentsPage(
|
const results: ViewModels.QueryResults = await queryDocumentsPage(
|
||||||
@@ -1776,7 +1791,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
this.currentDocDBQueryInfo.index
|
this.currentDocDBQueryInfo.index
|
||||||
);
|
);
|
||||||
|
|
||||||
GraphExplorer.clearConsoleProgress(id);
|
clearConsoleProgress();
|
||||||
this.currentDocDBQueryInfo.index = results.lastItemIndex + 1;
|
this.currentDocDBQueryInfo.index = results.lastItemIndex + 1;
|
||||||
this.setState({ hasMoreRoots: results.hasMoreResults });
|
this.setState({ hasMoreRoots: results.hasMoreResults });
|
||||||
RU = results.requestCharge.toString();
|
RU = results.requestCharge.toString();
|
||||||
@@ -1793,7 +1808,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
|
|
||||||
return { requestCharge: RU };
|
return { requestCharge: RU };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
GraphExplorer.clearConsoleProgress(id);
|
clearConsoleProgress();
|
||||||
const errorMsg = `Failed to query: ${this.currentDocDBQueryInfo.query}. Reason:${getErrorMessage(error)}`;
|
const errorMsg = `Failed to query: ${this.currentDocDBQueryInfo.query}. Reason:${getErrorMessage(error)}`;
|
||||||
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -2003,8 +2018,4 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static clearConsoleProgress(id: string) {
|
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { NeighborVertexBasicInfo } from "./GraphExplorer";
|
|
||||||
import * as GraphData from "./GraphData";
|
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
|
import * as GraphData from "./GraphData";
|
||||||
|
import { NeighborVertexBasicInfo } from "./GraphExplorer";
|
||||||
|
|
||||||
interface JoinArrayMaxCharOutput {
|
interface JoinArrayMaxCharOutput {
|
||||||
result: string; // string output
|
result: string; // string output
|
||||||
@@ -13,9 +13,9 @@ interface EdgePropertyType {
|
|||||||
inV?: string;
|
inV?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNeighborTitle(neighbor: NeighborVertexBasicInfo): string {
|
export const getNeighborTitle = (neighbor: NeighborVertexBasicInfo): string => {
|
||||||
return `edge id: ${neighbor.edgeId}, vertex id: ${neighbor.id}`;
|
return `edge id: ${neighbor.edgeId}, vertex id: ${neighbor.id}`;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect all edges from this node
|
* Collect all edges from this node
|
||||||
@@ -23,11 +23,11 @@ export function getNeighborTitle(neighbor: NeighborVertexBasicInfo): string {
|
|||||||
* @param graphData
|
* @param graphData
|
||||||
* @param newNodes (optional) object describing new nodes encountered
|
* @param newNodes (optional) object describing new nodes encountered
|
||||||
*/
|
*/
|
||||||
export function createEdgesfromNode(
|
export const createEdgesfromNode = (
|
||||||
vertex: GraphData.GremlinVertex,
|
vertex: GraphData.GremlinVertex,
|
||||||
graphData: GraphData.GraphData<GraphData.GremlinVertex, GraphData.GremlinEdge>,
|
graphData: GraphData.GraphData<GraphData.GremlinVertex, GraphData.GremlinEdge>,
|
||||||
newNodes?: { [id: string]: boolean }
|
newNodes?: { [id: string]: boolean }
|
||||||
): void {
|
): void => {
|
||||||
if (Object.prototype.hasOwnProperty.call(vertex, "outE")) {
|
if (Object.prototype.hasOwnProperty.call(vertex, "outE")) {
|
||||||
const outE = vertex.outE;
|
const outE = vertex.outE;
|
||||||
for (const label in outE) {
|
for (const label in outE) {
|
||||||
@@ -66,7 +66,7 @@ export function createEdgesfromNode(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* From ['id1', 'id2', 'idn'] build the following string "'id1','id2','idn'".
|
* From ['id1', 'id2', 'idn'] build the following string "'id1','id2','idn'".
|
||||||
@@ -75,7 +75,7 @@ export function createEdgesfromNode(
|
|||||||
* @param maxSize
|
* @param maxSize
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
export function getLimitedArrayString(array: string[], maxSize: number): JoinArrayMaxCharOutput {
|
export const getLimitedArrayString = (array: string[], maxSize: number): JoinArrayMaxCharOutput => {
|
||||||
if (!array || array.length === 0 || array[0].length + 2 > maxSize) {
|
if (!array || array.length === 0 || array[0].length + 2 > maxSize) {
|
||||||
return { result: "", consumedCount: 0 };
|
return { result: "", consumedCount: 0 };
|
||||||
}
|
}
|
||||||
@@ -96,16 +96,16 @@ export function getLimitedArrayString(array: string[], maxSize: number): JoinArr
|
|||||||
result: output,
|
result: output,
|
||||||
consumedCount: i + 1,
|
consumedCount: i + 1,
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export function createFetchEdgePairQuery(
|
export const createFetchEdgePairQuery = (
|
||||||
outE: boolean,
|
outE: boolean,
|
||||||
pkid: string,
|
pkid: string,
|
||||||
excludedEdgeIds: string[],
|
excludedEdgeIds: string[],
|
||||||
startIndex: number,
|
startIndex: number,
|
||||||
pageSize: number,
|
pageSize: number,
|
||||||
withoutStepArgMaxLenght: number
|
withoutStepArgMaxLenght: number
|
||||||
): string {
|
): string => {
|
||||||
let gremlinQuery: string;
|
let gremlinQuery: string;
|
||||||
if (excludedEdgeIds.length > 0) {
|
if (excludedEdgeIds.length > 0) {
|
||||||
// build a string up to max char
|
// build a string up to max char
|
||||||
@@ -128,15 +128,15 @@ export function createFetchEdgePairQuery(
|
|||||||
}().as('v').select('e', 'v')`;
|
}().as('v').select('e', 'v')`;
|
||||||
}
|
}
|
||||||
return gremlinQuery;
|
return gremlinQuery;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trim graph
|
* Trim graph
|
||||||
*/
|
*/
|
||||||
export function trimGraph(
|
export const trimGraph = (
|
||||||
currentRoot: GraphData.GremlinVertex,
|
currentRoot: GraphData.GremlinVertex,
|
||||||
graphData: GraphData.GraphData<GraphData.GremlinVertex, GraphData.GremlinEdge>
|
graphData: GraphData.GraphData<GraphData.GremlinVertex, GraphData.GremlinEdge>
|
||||||
) {
|
): void => {
|
||||||
const importantNodes = [currentRoot.id].concat(currentRoot._ancestorsId);
|
const importantNodes = [currentRoot.id].concat(currentRoot._ancestorsId);
|
||||||
graphData.unloadAllVertices(importantNodes);
|
graphData.unloadAllVertices(importantNodes);
|
||||||
|
|
||||||
@@ -144,32 +144,32 @@ export function trimGraph(
|
|||||||
$.each(graphData.ids, (index: number, id: string) => {
|
$.each(graphData.ids, (index: number, id: string) => {
|
||||||
graphData.getVertexById(id)._isFixedPosition = importantNodes.indexOf(id) !== -1;
|
graphData.getVertexById(id)._isFixedPosition = importantNodes.indexOf(id) !== -1;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export function addRootChildToGraph(
|
export const addRootChildToGraph = (
|
||||||
root: GraphData.GremlinVertex,
|
root: GraphData.GremlinVertex,
|
||||||
child: GraphData.GremlinVertex,
|
child: GraphData.GremlinVertex,
|
||||||
graphData: GraphData.GraphData<GraphData.GremlinVertex, GraphData.GremlinEdge>
|
graphData: GraphData.GraphData<GraphData.GremlinVertex, GraphData.GremlinEdge>
|
||||||
) {
|
): void => {
|
||||||
child._ancestorsId = (root._ancestorsId || []).concat([root.id]);
|
child._ancestorsId = (root._ancestorsId || []).concat([root.id]);
|
||||||
graphData.addVertex(child);
|
graphData.addVertex(child);
|
||||||
createEdgesfromNode(child, graphData);
|
createEdgesfromNode(child, graphData);
|
||||||
graphData.addNeighborInfo(child);
|
graphData.addNeighborInfo(child);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO Perform minimal substitution to prevent breaking gremlin query and allow \"" for now.
|
* TODO Perform minimal substitution to prevent breaking gremlin query and allow \"" for now.
|
||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
export function escapeDoubleQuotes(value: string): string {
|
export const escapeDoubleQuotes = (value: string): string => {
|
||||||
return value === undefined ? value : value.replace(/"/g, '\\"');
|
return value === undefined ? value : value.replace(/"/g, '\\"');
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Surround with double-quotes if val is a string.
|
* Surround with double-quotes if val is a string.
|
||||||
* @param val
|
* @param val
|
||||||
*/
|
*/
|
||||||
export function getQuotedPropValue(ip: ViewModels.InputPropertyValue): string {
|
export const getQuotedPropValue = (ip: ViewModels.InputPropertyValue): string => {
|
||||||
switch (ip.type) {
|
switch (ip.type) {
|
||||||
case "number":
|
case "number":
|
||||||
case "boolean":
|
case "boolean":
|
||||||
@@ -179,12 +179,12 @@ export function getQuotedPropValue(ip: ViewModels.InputPropertyValue): string {
|
|||||||
default:
|
default:
|
||||||
return `"${escapeDoubleQuotes(ip.value as string)}"`;
|
return `"${escapeDoubleQuotes(ip.value as string)}"`;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO Perform minimal substitution to prevent breaking gremlin query and allow \' for now.
|
* TODO Perform minimal substitution to prevent breaking gremlin query and allow \' for now.
|
||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
export function escapeSingleQuotes(value: string): string {
|
export const escapeSingleQuotes = (value: string): string => {
|
||||||
return value === undefined ? value : value.replace(/'/g, "\\'");
|
return value === undefined ? value : value.replace(/'/g, "\\'");
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -3,11 +3,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Q from "q";
|
import * as Q from "q";
|
||||||
import { GremlinSimpleClient, Result } from "./GremlinSimpleClient";
|
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import { HashMap } from "../../../Common/HashMap";
|
|
||||||
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
import { HashMap } from "../../../Common/HashMap";
|
||||||
|
import { logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
|
||||||
|
import { GremlinSimpleClient, Result } from "./GremlinSimpleClient";
|
||||||
|
|
||||||
export interface GremlinClientParameters {
|
export interface GremlinClientParameters {
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
@@ -77,9 +76,7 @@ export class GremlinClient {
|
|||||||
this.abortPendingRequest(requestId, errorMessage, result.requestCharge);
|
this.abortPendingRequest(requestId, errorMessage, result.requestCharge);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
infoCallback: (msg: string) => {
|
infoCallback: logConsoleInfo,
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import { NewVertexComponent, NewVertexViewModel } from "./NewVertexComponent";
|
|
||||||
|
|
||||||
const component = NewVertexComponent;
|
|
||||||
|
|
||||||
describe("New Vertex Component", () => {
|
|
||||||
let vm: NewVertexViewModel;
|
|
||||||
let partitionKeyProperty: ko.Observable<string>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
document.body.innerHTML = component.template as any;
|
|
||||||
partitionKeyProperty = ko.observable(null);
|
|
||||||
vm = new component.viewModel({
|
|
||||||
newVertexData: null,
|
|
||||||
partitionKeyProperty,
|
|
||||||
});
|
|
||||||
ko.applyBindings(vm);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
ko.cleanNode(document);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Rendering", () => {
|
|
||||||
it("should display property list with input and +Add Property", () => {
|
|
||||||
expect(document.querySelector(".newVertexComponent .newVertexForm")).not.toBeNull();
|
|
||||||
expect(document.querySelector(".newVertexComponent .edgeInput")).not.toBeNull();
|
|
||||||
expect(document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn")).not.toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should display partition key property if set", () => {
|
|
||||||
partitionKeyProperty("testKey");
|
|
||||||
expect(
|
|
||||||
(document.querySelector(".newVertexComponent .newVertexForm .labelCol input") as HTMLInputElement).value
|
|
||||||
).toEqual("testKey");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should NOT display partition key property if NOT set", () => {
|
|
||||||
expect(document.getElementsByClassName("valueCol").length).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Behavior", () => {
|
|
||||||
let clickSpy: jasmine.Spy;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
clickSpy = jasmine.createSpy("Command button click spy");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should add new property row when +Add property button is pressed", () => {
|
|
||||||
document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click"));
|
|
||||||
document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click"));
|
|
||||||
document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click"));
|
|
||||||
expect(document.getElementsByClassName("valueCol").length).toBe(3);
|
|
||||||
expect(document.getElementsByClassName("rightPaneTrashIcon").length).toBe(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should remove property row when trash button is pressed", () => {
|
|
||||||
document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click"));
|
|
||||||
document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click"));
|
|
||||||
|
|
||||||
// Mark this one to delete
|
|
||||||
const elts = document.querySelectorAll(".newVertexComponent .rightPaneTrashIconImg");
|
|
||||||
elts[elts.length - 1].className += " deleteme";
|
|
||||||
|
|
||||||
document.querySelector(".newVertexComponent .rightPaneAddPropertyBtn").dispatchEvent(new Event("click"));
|
|
||||||
document
|
|
||||||
.querySelector(".newVertexComponent .rightPaneTrashIconImg.deleteme")
|
|
||||||
.parentElement.dispatchEvent(new Event("click"));
|
|
||||||
expect(document.getElementsByClassName("valueCol").length).toBe(2);
|
|
||||||
expect(document.getElementsByClassName("rightPaneTrashIcon").length).toBe(2);
|
|
||||||
expect(document.querySelectorAll(".newVertexComponent .rightPaneTrashIconImg.deleteme").length).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
<div class="newVertexComponent" data-bind="setTemplateReady: true">
|
|
||||||
<div class="newVertexForm">
|
|
||||||
<div class="newVertexFormRow">
|
|
||||||
<label for="VertexLabel" class="labelCol">Label</label>
|
|
||||||
<input
|
|
||||||
class="edgeInput"
|
|
||||||
type="text"
|
|
||||||
data-bind="textInput:$data.newVertexData().label, hasFocus: $data.firstFieldHasFocus"
|
|
||||||
aria-label="Enter vertex label"
|
|
||||||
role="textbox"
|
|
||||||
tabindex="0"
|
|
||||||
placeholder="Enter vertex label"
|
|
||||||
autocomplete="off"
|
|
||||||
id="VertexLabel"
|
|
||||||
/>
|
|
||||||
<div class="actionCol"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ko foreach:{ data:newVertexData().properties, as: 'property' } -->
|
|
||||||
<div class="newVertexFormRow">
|
|
||||||
<div class="labelCol">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="propertyKeyNewVertexPane"
|
|
||||||
data-bind="textInput: property.key, attr: { 'aria-label': 'Enter key for property '+ ($index() + 1) }"
|
|
||||||
placeholder="Key"
|
|
||||||
autocomplete="off"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="valueCol">
|
|
||||||
<input
|
|
||||||
class="edgeInput"
|
|
||||||
type="text"
|
|
||||||
data-bind="textInput: property.values[0].value, , attr: { 'aria-label': 'Enter value for property '+ ($index() + 1) }"
|
|
||||||
placeholder="Value"
|
|
||||||
autocomplete="off"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<select
|
|
||||||
class="typeSelect"
|
|
||||||
required
|
|
||||||
data-bind="options:$parent.propertyTypes, value:property.values[0].type, attr: { 'aria-label': property.values[0].type + ': for property '+ ($index() + 1) }"
|
|
||||||
></select>
|
|
||||||
</div>
|
|
||||||
<div class="actionCol">
|
|
||||||
<div
|
|
||||||
class="rightPaneTrashIcon rightPaneBtns"
|
|
||||||
data-bind="click:$parent.removeNewVertexProperty.bind($parent, $index()), event: { keypress: $parent.removeNewVertexPropertyKeyPress.bind($parent, $index()) }, attr: { 'aria-label': 'Remove property '+ ($index() + 1) }"
|
|
||||||
tabindex="0"
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
<img class="refreshcol rightPaneTrashIconImg" src="/delete.svg" alt="Remove property" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- /ko -->
|
|
||||||
|
|
||||||
<div class="newVertexFormRow">
|
|
||||||
<span class="rightPaneAddPropertyBtnPadding">
|
|
||||||
<span
|
|
||||||
class="rightPaneAddPropertyBtn rightPaneBtns"
|
|
||||||
id="addProperyNewVertexBtn"
|
|
||||||
data-bind="click:onAddNewProperty, event: { keypress: onAddNewPropertyKeyPress }"
|
|
||||||
tabindex="0"
|
|
||||||
role="button"
|
|
||||||
>
|
|
||||||
<img class="refreshcol rightPaneAddPropertyImg" src="/Add-property.svg" alt="Add property" /> Add
|
|
||||||
Property</span
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
@import "../../../../less/Common/Constants";
|
||||||
|
|
||||||
|
.newVertexComponent {
|
||||||
|
padding: @LargeSpace 20px 20px 0px;
|
||||||
|
width: 400px;
|
||||||
|
|
||||||
|
.newVertexForm {
|
||||||
|
width: 100%;
|
||||||
|
.flex-display();
|
||||||
|
.flex-direction();
|
||||||
|
|
||||||
|
.newVertexFormRow {
|
||||||
|
.flex-display();
|
||||||
|
.flex-direction(@direction: row);
|
||||||
|
padding: 4px 5px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.valueCol {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rightPaneAddPropertyBtnPadding {
|
||||||
|
padding-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edgeLabel {
|
||||||
|
padding-right: 41px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionCol {
|
||||||
|
min-width: 30px;
|
||||||
|
padding: 0px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labelCol {
|
||||||
|
width: 72px;
|
||||||
|
min-width: 72px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
max-width: 65px;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.edgeInput {
|
||||||
|
width: 100%;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeSelect {
|
||||||
|
height: 23px;
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rightPaneTrashIcon {
|
||||||
|
padding: 4px 1px 0px 4px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rightPaneTrashIconImg {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rightPaneAddPropertyBtn {
|
||||||
|
padding: 7px 7px 8px 8px;
|
||||||
|
margin-left: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rightPaneBtns {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: @BaseLow;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: @AccentMediumLow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rightPaneAddPropertyImg {
|
||||||
|
margin-right: 5px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentScroll {
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import { fireEvent, render, screen } from "@testing-library/react";
|
||||||
|
import React from "react";
|
||||||
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
|
import { NewVertexComponent } from "./NewVertexComponent";
|
||||||
|
|
||||||
|
describe("New Vertex Component", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const fakeNewVertexData: ViewModels.NewVertexData = {
|
||||||
|
label: "",
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
key: "test1",
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
value: "",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const props = {
|
||||||
|
newVertexDataProp: fakeNewVertexData,
|
||||||
|
partitionKeyPropertyProp: "test1",
|
||||||
|
onChangeProp: (): void => undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<NewVertexComponent {...props} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render default prpoerty", () => {
|
||||||
|
const fakeNewVertexData: ViewModels.NewVertexData = {
|
||||||
|
label: "",
|
||||||
|
properties: [],
|
||||||
|
};
|
||||||
|
const props = {
|
||||||
|
newVertexDataProp: fakeNewVertexData,
|
||||||
|
partitionKeyPropertyProp: "",
|
||||||
|
onChangeProp: (): void => undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { asFragment } = render(<NewVertexComponent {...props} />);
|
||||||
|
expect(asFragment).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render Add property button", () => {
|
||||||
|
const span = screen.getByText("Add Property");
|
||||||
|
expect(span).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call onAddNewProperty method on span click", () => {
|
||||||
|
const onAddNewProperty = jest.fn();
|
||||||
|
const span = screen.getByText("Add Property");
|
||||||
|
span.onclick = onAddNewProperty();
|
||||||
|
fireEvent.click(span);
|
||||||
|
expect(onAddNewProperty).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call onAddNewPropertyKeyPress method on span keyPress", () => {
|
||||||
|
const onAddNewPropertyKeyPress = jest.fn();
|
||||||
|
const span = screen.getByText("Add Property");
|
||||||
|
span.onkeypress = onAddNewPropertyKeyPress();
|
||||||
|
fireEvent.keyPress(span, { key: "Enter", code: 13, charCode: 13 });
|
||||||
|
expect(onAddNewPropertyKeyPress).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call onLabelChange method on input change", () => {
|
||||||
|
const onLabelChange = jest.fn();
|
||||||
|
const input = screen.getByLabelText("Label");
|
||||||
|
input.onchange = onLabelChange();
|
||||||
|
fireEvent.change(input, { target: { value: "Label" } });
|
||||||
|
expect(onLabelChange).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call onKeyChange method on key input change", () => {
|
||||||
|
const onKeyChange = jest.fn();
|
||||||
|
const input = screen.queryByPlaceholderText("Key");
|
||||||
|
input.onchange = onKeyChange();
|
||||||
|
fireEvent.change(input, { target: { value: "pk1" } });
|
||||||
|
expect(onKeyChange).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call onValueChange method on value input change", () => {
|
||||||
|
const onValueChange = jest.fn();
|
||||||
|
const input = screen.queryByPlaceholderText("Value");
|
||||||
|
input.onchange = onValueChange();
|
||||||
|
fireEvent.change(input, { target: { value: "abc" } });
|
||||||
|
expect(onValueChange).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call removeNewVertexProperty method on remove button click", () => {
|
||||||
|
const removeNewVertexProperty = jest.fn();
|
||||||
|
const div = screen.getAllByRole("button");
|
||||||
|
div[0].onclick = removeNewVertexProperty();
|
||||||
|
fireEvent.click(div[0]);
|
||||||
|
expect(removeNewVertexProperty).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call removeNewVertexProperty method on remove button keyPress", () => {
|
||||||
|
const removeNewVertexPropertyKeyPress = jest.fn();
|
||||||
|
const div = screen.getAllByRole("button");
|
||||||
|
div[0].onkeypress = removeNewVertexPropertyKeyPress();
|
||||||
|
fireEvent.keyPress(div[0], { key: "Enter", code: 13, charCode: 13 });
|
||||||
|
expect(removeNewVertexPropertyKeyPress).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call onTypeChange method on type dropdown change", () => {
|
||||||
|
const DOWN_ARROW = { keyCode: 40 };
|
||||||
|
const onTypeChange = jest.fn();
|
||||||
|
const dropdown = screen.getByRole("listbox");
|
||||||
|
dropdown.onclick = onTypeChange();
|
||||||
|
dropdown.onkeydown = onTypeChange();
|
||||||
|
|
||||||
|
fireEvent.keyDown(screen.getByRole("listbox"), DOWN_ARROW);
|
||||||
|
fireEvent.click(screen.getByText(/number/));
|
||||||
|
expect(onTypeChange).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import { EditorNodePropertiesComponent } from "../GraphExplorerComponent/EditorNodePropertiesComponent";
|
|
||||||
import { NewVertexData, InputProperty } from "../../../Contracts/ViewModels";
|
|
||||||
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
|
|
||||||
import * as Constants from "../../../Common/Constants";
|
|
||||||
import template from "./NewVertexComponent.html";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parameters for this component
|
|
||||||
*/
|
|
||||||
export interface NewVertexParams {
|
|
||||||
// Data to be edited by the component
|
|
||||||
newVertexData: ko.Observable<NewVertexData>;
|
|
||||||
partitionKeyProperty: ko.Observable<string>;
|
|
||||||
firstFieldHasFocus?: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback triggered when the template is bound to the component (for testing purposes)
|
|
||||||
*/
|
|
||||||
onTemplateReady?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NewVertexViewModel extends WaitsForTemplateViewModel {
|
|
||||||
private static readonly DEFAULT_PROPERTY_TYPE = "string";
|
|
||||||
|
|
||||||
private newVertexData: ko.Observable<NewVertexData>;
|
|
||||||
private firstFieldHasFocus: ko.Observable<boolean>;
|
|
||||||
private propertyTypes: string[];
|
|
||||||
|
|
||||||
public constructor(params: NewVertexParams) {
|
|
||||||
super();
|
|
||||||
super.onTemplateReady((isTemplateReady: boolean) => {
|
|
||||||
if (isTemplateReady && params.onTemplateReady) {
|
|
||||||
params.onTemplateReady();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.newVertexData =
|
|
||||||
params.newVertexData ||
|
|
||||||
ko.observable({
|
|
||||||
label: "",
|
|
||||||
properties: <InputProperty[]>[],
|
|
||||||
});
|
|
||||||
this.firstFieldHasFocus = params.firstFieldHasFocus || ko.observable(false);
|
|
||||||
this.propertyTypes = EditorNodePropertiesComponent.VERTEX_PROPERTY_TYPES;
|
|
||||||
if (params.partitionKeyProperty) {
|
|
||||||
params.partitionKeyProperty.subscribe((newKeyProp: string) => {
|
|
||||||
if (!newKeyProp) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.addNewVertexProperty(newKeyProp);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public onAddNewProperty() {
|
|
||||||
this.addNewVertexProperty();
|
|
||||||
document.getElementById("propertyKeyNewVertexPane").focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
public onAddNewPropertyKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
|
||||||
if (event.keyCode === Constants.KeyCodes.Enter || event.keyCode === Constants.KeyCodes.Space) {
|
|
||||||
this.onAddNewProperty();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
public addNewVertexProperty(key?: string) {
|
|
||||||
let ap = this.newVertexData().properties;
|
|
||||||
ap.push({ key: key || "", values: [{ value: "", type: NewVertexViewModel.DEFAULT_PROPERTY_TYPE }] });
|
|
||||||
this.newVertexData.valueHasMutated();
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeNewVertexProperty(index: number) {
|
|
||||||
let ap = this.newVertexData().properties;
|
|
||||||
ap.splice(index, 1);
|
|
||||||
this.newVertexData.valueHasMutated();
|
|
||||||
document.getElementById("addProperyNewVertexBtn").focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeNewVertexPropertyKeyPress = (index: number, source: any, event: KeyboardEvent): boolean => {
|
|
||||||
if (event.keyCode === Constants.KeyCodes.Enter || event.keyCode === Constants.KeyCodes.Space) {
|
|
||||||
this.removeNewVertexProperty(index);
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class for ko component registration
|
|
||||||
*/
|
|
||||||
export const NewVertexComponent = {
|
|
||||||
viewModel: NewVertexViewModel,
|
|
||||||
template,
|
|
||||||
};
|
|
||||||
213
src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx
Normal file
213
src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
import { Dropdown, IDropdownOption, Stack, TextField } from "office-ui-fabric-react";
|
||||||
|
import React, { FunctionComponent, useRef, useState } from "react";
|
||||||
|
import AddIcon from "../../../../images/Add-property.svg";
|
||||||
|
import DeleteIcon from "../../../../images/delete.svg";
|
||||||
|
import { NormalizedEventKey } from "../../../Common/Constants";
|
||||||
|
import { GremlinPropertyValueType, InputPropertyValueTypeString, NewVertexData } from "../../../Contracts/ViewModels";
|
||||||
|
import { EditorNodePropertiesComponent } from "../GraphExplorerComponent/EditorNodePropertiesComponent";
|
||||||
|
import "./NewVertexComponent.less";
|
||||||
|
export interface INewVertexComponentProps {
|
||||||
|
newVertexDataProp: NewVertexData;
|
||||||
|
partitionKeyPropertyProp: string;
|
||||||
|
onChangeProp: (labelData: NewVertexData) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = ({
|
||||||
|
newVertexDataProp,
|
||||||
|
partitionKeyPropertyProp,
|
||||||
|
onChangeProp,
|
||||||
|
}: INewVertexComponentProps): JSX.Element => {
|
||||||
|
const DEFAULT_PROPERTY_TYPE = "string";
|
||||||
|
const [newVertexData, setNewVertexData] = useState<NewVertexData>(
|
||||||
|
newVertexDataProp || {
|
||||||
|
label: "",
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
key: partitionKeyPropertyProp,
|
||||||
|
values: [{ value: "", type: DEFAULT_PROPERTY_TYPE }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const propertyTypes: string[] = EditorNodePropertiesComponent.VERTEX_PROPERTY_TYPES;
|
||||||
|
const input = useRef(undefined);
|
||||||
|
|
||||||
|
const onAddNewProperty = () => {
|
||||||
|
addNewVertexProperty();
|
||||||
|
setTimeout(() => {
|
||||||
|
input.current.focus();
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAddNewPropertyKeyPress = (event: React.KeyboardEvent) => {
|
||||||
|
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
|
||||||
|
onAddNewProperty();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addNewVertexProperty = () => {
|
||||||
|
let key: string;
|
||||||
|
const ap = newVertexData.properties;
|
||||||
|
if (ap.length === 0) {
|
||||||
|
key = partitionKeyPropertyProp;
|
||||||
|
}
|
||||||
|
ap.push({
|
||||||
|
key: key || "",
|
||||||
|
values: [{ value: "", type: DEFAULT_PROPERTY_TYPE }],
|
||||||
|
});
|
||||||
|
setNewVertexData((prevData) => ({
|
||||||
|
...prevData,
|
||||||
|
properties: ap,
|
||||||
|
}));
|
||||||
|
onChangeProp(newVertexData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeNewVertexProperty = (event?: React.MouseEvent<HTMLDivElement>, index?: number) => {
|
||||||
|
const ap = newVertexData.properties;
|
||||||
|
ap.splice(index, 1);
|
||||||
|
setNewVertexData((prevData) => ({
|
||||||
|
...prevData,
|
||||||
|
properties: ap,
|
||||||
|
}));
|
||||||
|
onChangeProp(newVertexData);
|
||||||
|
document.getElementById("addProperyNewVertexBtn").focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeNewVertexPropertyKeyPress = (event: React.KeyboardEvent<HTMLDivElement>, index: number) => {
|
||||||
|
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
|
||||||
|
removeNewVertexProperty(undefined, index);
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLabelChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setNewVertexData((prevData) => ({
|
||||||
|
...prevData,
|
||||||
|
label: event.target.value,
|
||||||
|
}));
|
||||||
|
onChangeProp(newVertexData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onKeyChange = (event: React.ChangeEvent<HTMLInputElement>, index: number) => {
|
||||||
|
const newState = { ...newVertexData };
|
||||||
|
newState.properties[index].key = event.target.value;
|
||||||
|
setNewVertexData(newState);
|
||||||
|
onChangeProp(newVertexData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onValueChange = (event: React.ChangeEvent<HTMLInputElement>, index: number) => {
|
||||||
|
const newState = { ...newVertexData };
|
||||||
|
newState.properties[index].values[0].value = event.target.value as GremlinPropertyValueType;
|
||||||
|
setNewVertexData(newState);
|
||||||
|
onChangeProp(newVertexData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTypeChange = (option: string, index: number) => {
|
||||||
|
const newState = { ...newVertexData };
|
||||||
|
if (newState.properties[index]) {
|
||||||
|
newState.properties[index].values[0].type = option as InputPropertyValueTypeString;
|
||||||
|
setNewVertexData(newState);
|
||||||
|
onChangeProp(newVertexData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<div className="newVertexComponent">
|
||||||
|
<div className="newVertexForm">
|
||||||
|
<div className="newVertexFormRow">
|
||||||
|
<TextField
|
||||||
|
label="Label"
|
||||||
|
className="edgeInput"
|
||||||
|
type="text"
|
||||||
|
ariaLabel="Enter vertex label"
|
||||||
|
role="textbox"
|
||||||
|
tabIndex={0}
|
||||||
|
placeholder="Enter vertex label"
|
||||||
|
autoComplete="off"
|
||||||
|
id="VertexLabel"
|
||||||
|
value={newVertexData.label}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
onLabelChange(event);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="actionCol"></div>
|
||||||
|
</div>
|
||||||
|
{newVertexData.properties.map((data, index) => {
|
||||||
|
return (
|
||||||
|
<div key={index} className="newVertexFormRow">
|
||||||
|
<div className="labelCol">
|
||||||
|
<TextField
|
||||||
|
className="edgeInput"
|
||||||
|
type="text"
|
||||||
|
id="propertyKeyNewVertexPane"
|
||||||
|
componentRef={input}
|
||||||
|
placeholder="Key"
|
||||||
|
autoComplete="off"
|
||||||
|
aria-label={`Enter value for propery ${index + 1}`}
|
||||||
|
value={data.key}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onKeyChange(event, index)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="valueCol">
|
||||||
|
<TextField
|
||||||
|
className="edgeInput"
|
||||||
|
type="text"
|
||||||
|
placeholder="Value"
|
||||||
|
autoComplete="off"
|
||||||
|
aria-label={`Enter value for propery ${index + 1}`}
|
||||||
|
value={data.values[0].value.toString()}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onValueChange(event, index)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Dropdown
|
||||||
|
role="listbox"
|
||||||
|
placeholder="Select an option"
|
||||||
|
defaultSelectedKey={data.values[0].type}
|
||||||
|
style={{ width: 100 }}
|
||||||
|
options={propertyTypes.map((type) => ({
|
||||||
|
key: type,
|
||||||
|
text: type,
|
||||||
|
}))}
|
||||||
|
onChange={(_, options: IDropdownOption) => onTypeChange(options.key.toString(), index)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="actionCol">
|
||||||
|
<div
|
||||||
|
className="rightPaneTrashIcon rightPaneBtns"
|
||||||
|
tabIndex={0}
|
||||||
|
role="button"
|
||||||
|
onClick={(event: React.MouseEvent<HTMLDivElement>) => removeNewVertexProperty(event, index)}
|
||||||
|
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) =>
|
||||||
|
removeNewVertexPropertyKeyPress(event, index)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<img className="refreshcol rightPaneTrashIconImg" src={DeleteIcon} alt="Remove property" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
<div className="newVertexFormRow">
|
||||||
|
<span className="rightPaneAddPropertyBtnPadding">
|
||||||
|
<span
|
||||||
|
className="rightPaneAddPropertyBtn rightPaneBtns"
|
||||||
|
id="addProperyNewVertexBtn"
|
||||||
|
tabIndex={0}
|
||||||
|
role="button"
|
||||||
|
onClick={onAddNewProperty}
|
||||||
|
onKeyPress={(event: React.KeyboardEvent<HTMLSpanElement>) => onAddNewPropertyKeyPress(event)}
|
||||||
|
>
|
||||||
|
<img className="refreshcol rightPaneAddPropertyImg" src={AddIcon} alt="Add property" /> Add Property
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`New Vertex Component should render default prpoerty 1`] = `[Function]`;
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
@import "../../../../less/Common/Constants";
|
|
||||||
|
|
||||||
.newVertexComponent {
|
|
||||||
padding: @LargeSpace 20px 20px 0px;
|
|
||||||
width: 400px;
|
|
||||||
|
|
||||||
.newVertexForm {
|
|
||||||
width: 100%;
|
|
||||||
.flex-display();
|
|
||||||
.flex-direction();
|
|
||||||
|
|
||||||
.newVertexFormRow {
|
|
||||||
.flex-display();
|
|
||||||
.flex-direction(@direction: row);
|
|
||||||
padding: 4px 5px;
|
|
||||||
|
|
||||||
label {
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.valueCol {
|
|
||||||
flex-grow: 1;
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightPaneAddPropertyBtnPadding {
|
|
||||||
padding-top: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.edgeLabel {
|
|
||||||
padding-right: 41px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.actionCol {
|
|
||||||
min-width: 30px;
|
|
||||||
padding: 0px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.labelCol {
|
|
||||||
width: 72px;
|
|
||||||
min-width: 72px;
|
|
||||||
|
|
||||||
input {
|
|
||||||
max-width: 65px;
|
|
||||||
padding-left: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.edgeInput {
|
|
||||||
width: 100%;
|
|
||||||
padding-left: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typeSelect {
|
|
||||||
height: 23px;
|
|
||||||
width: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightPaneTrashIcon {
|
|
||||||
padding: 4px 1px 0px 4px;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightPaneTrashIconImg {
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightPaneAddPropertyBtn {
|
|
||||||
padding: 7px 7px 8px 8px;
|
|
||||||
margin-left: -8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightPaneBtns {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: @BaseLow ;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: @AccentMediumLow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightPaneAddPropertyImg {
|
|
||||||
margin-right: 5px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contentScroll {
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,15 +4,15 @@
|
|||||||
* and update any knockout observables passed from the parent.
|
* and update any knockout observables passed from the parent.
|
||||||
*/
|
*/
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
|
import { CommandBar, ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
|
||||||
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
|
||||||
import { CommandBar, ICommandBarItemProps } from "office-ui-fabric-react/lib/CommandBar";
|
|
||||||
import { StyleConstants } from "../../../Common/Constants";
|
import { StyleConstants } from "../../../Common/Constants";
|
||||||
import * as CommandBarUtil from "./CommandBarUtil";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import Explorer from "../../Explorer";
|
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import * as CommandBarComponentButtonFactory from "./CommandBarComponentButtonFactory";
|
||||||
|
import * as CommandBarUtil from "./CommandBarUtil";
|
||||||
|
|
||||||
export class CommandBarComponentAdapter implements ReactAdapter {
|
export class CommandBarComponentAdapter implements ReactAdapter {
|
||||||
public parameters: ko.Observable<number>;
|
public parameters: ko.Observable<number>;
|
||||||
@@ -23,17 +23,13 @@ export class CommandBarComponentAdapter implements ReactAdapter {
|
|||||||
constructor(container: Explorer) {
|
constructor(container: Explorer) {
|
||||||
this.container = container;
|
this.container = container;
|
||||||
this.tabsButtons = [];
|
this.tabsButtons = [];
|
||||||
this.isNotebookTabActive = ko.computed(() =>
|
this.isNotebookTabActive = ko.computed(
|
||||||
container.tabsManager.isTabActive(ViewModels.CollectionTabKind.NotebookV2)
|
() => container.tabsManager.activeTab()?.tabKind === ViewModels.CollectionTabKind.NotebookV2
|
||||||
);
|
);
|
||||||
|
|
||||||
// These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates
|
// These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates
|
||||||
const toWatch = [
|
const toWatch = [
|
||||||
container.isPreferredApiTable,
|
|
||||||
container.isPreferredApiMongoDB,
|
container.isPreferredApiMongoDB,
|
||||||
container.isPreferredApiDocumentDB,
|
|
||||||
container.isPreferredApiCassandra,
|
|
||||||
container.isPreferredApiGraph,
|
|
||||||
container.deleteCollectionText,
|
container.deleteCollectionText,
|
||||||
container.deleteDatabaseText,
|
container.deleteDatabaseText,
|
||||||
container.addCollectionText,
|
container.addCollectionText,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import { AuthType } from "../../../AuthType";
|
import { AuthType } from "../../../AuthType";
|
||||||
|
import { DatabaseAccount } from "../../../Contracts/DataModels";
|
||||||
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
||||||
import { updateUserContext } from "../../../UserContext";
|
import { updateUserContext } from "../../../UserContext";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
@@ -15,9 +16,14 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mockExplorer = {} as Explorer;
|
mockExplorer = {} as Explorer;
|
||||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
mockExplorer.addCollectionText = ko.observable("mockText");
|
||||||
mockExplorer.isPreferredApiTable = ko.computed(() => true);
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableTable" }],
|
||||||
|
},
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
|
mockExplorer.isPreferredApiMongoDB = 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.isSynapseLinkUpdating = ko.observable(false);
|
||||||
|
|
||||||
@@ -54,9 +60,14 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mockExplorer = {} as Explorer;
|
mockExplorer = {} as Explorer;
|
||||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
mockExplorer.addCollectionText = ko.observable("mockText");
|
||||||
mockExplorer.isPreferredApiTable = ko.computed(() => true);
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableTable" }],
|
||||||
|
},
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
|
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
|
||||||
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
|
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||||
@@ -118,8 +129,13 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mockExplorer = {} as Explorer;
|
mockExplorer = {} as Explorer;
|
||||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
mockExplorer.addCollectionText = ko.observable("mockText");
|
||||||
mockExplorer.isPreferredApiTable = ko.computed(() => true);
|
updateUserContext({
|
||||||
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
|
databaseAccount: {
|
||||||
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableTable" }],
|
||||||
|
},
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||||
|
|
||||||
@@ -197,8 +213,15 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mockExplorer = {} as Explorer;
|
mockExplorer = {} as Explorer;
|
||||||
|
mockExplorer.addDatabaseText = ko.observable("mockText");
|
||||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
mockExplorer.addCollectionText = ko.observable("mockText");
|
||||||
mockExplorer.isPreferredApiTable = ko.computed(() => true);
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableTable" }],
|
||||||
|
},
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
|
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||||
@@ -208,15 +231,27 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => true);
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableCassandra" }],
|
||||||
|
},
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
mockExplorer.isNotebookEnabled = ko.observable(false);
|
mockExplorer.isNotebookEnabled = ko.observable(false);
|
||||||
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
|
||||||
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
|
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Cassandra Api not available - button should be hidden", () => {
|
it("Cassandra Api not available - button should be hidden", () => {
|
||||||
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableMongo" }],
|
||||||
|
},
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
|
console.log(mockExplorer);
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||||
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
const openCassandraShellBtn = buttons.find((button) => button.commandButtonLabel === openCassandraShellBtnLabel);
|
||||||
expect(openCassandraShellBtn).toBeUndefined();
|
expect(openCassandraShellBtn).toBeUndefined();
|
||||||
@@ -279,9 +314,14 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mockExplorer = {} as Explorer;
|
mockExplorer = {} as Explorer;
|
||||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
mockExplorer.addCollectionText = ko.observable("mockText");
|
||||||
mockExplorer.isPreferredApiTable = ko.computed(() => true);
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableTable" }],
|
||||||
|
},
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
|
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
|
||||||
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
|
|
||||||
|
|
||||||
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
mockExplorer.isSynapseLinkUpdating = ko.observable(false);
|
||||||
mockExplorer.isSparkEnabled = ko.observable(true);
|
mockExplorer.isSparkEnabled = ko.observable(true);
|
||||||
@@ -337,7 +377,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
mockExplorer = {} as Explorer;
|
mockExplorer = {} as Explorer;
|
||||||
mockExplorer.addCollectionText = ko.observable("mockText");
|
mockExplorer.addCollectionText = ko.observable("mockText");
|
||||||
mockExplorer.isPreferredApiDocumentDB = ko.computed(() => true);
|
|
||||||
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
|
||||||
mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true);
|
mockExplorer.isResourceTokenCollectionNodeSelected = ko.computed(() => true);
|
||||||
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
mockExplorer.isServerlessEnabled = ko.computed<boolean>(() => false);
|
||||||
@@ -347,6 +386,11 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should only show New SQL Query and Open Query buttons", () => {
|
it("should only show New SQL Query and Open Query buttons", () => {
|
||||||
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
|
kind: "DocumentDB",
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer);
|
||||||
expect(buttons.length).toBe(2);
|
expect(buttons.length).toBe(2);
|
||||||
expect(buttons[0].commandButtonLabel).toBe("New SQL Query");
|
expect(buttons[0].commandButtonLabel).toBe("New SQL Query");
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
|
|||||||
buttons.push(addSynapseLink);
|
buttons.push(addSynapseLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!container.isPreferredApiTable()) {
|
if (userContext.apiType !== "Tables") {
|
||||||
newCollectionBtn.children = [createNewCollectionGroup(container)];
|
newCollectionBtn.children = [createNewCollectionGroup(container)];
|
||||||
const newDatabaseBtn = createNewDatabase(container);
|
const newDatabaseBtn = createNewDatabase(container);
|
||||||
newCollectionBtn.children.push(newDatabaseBtn);
|
newCollectionBtn.children.push(newDatabaseBtn);
|
||||||
@@ -74,7 +74,7 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
|
|||||||
buttons.push(createOpenMongoTerminalButton(container));
|
buttons.push(createOpenMongoTerminalButton(container));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container.isPreferredApiCassandra()) {
|
if (userContext.apiType === "Cassandra") {
|
||||||
buttons.push(createOpenCassandraTerminalButton(container));
|
buttons.push(createOpenCassandraTerminalButton(container));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,15 +90,15 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
|
|||||||
buttons.push(createDivider());
|
buttons.push(createDivider());
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSqlQuerySupported = container.isPreferredApiDocumentDB() || container.isPreferredApiGraph();
|
const isSqlQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
|
||||||
if (isSqlQuerySupported) {
|
if (isSqlQuerySupported) {
|
||||||
const newSqlQueryBtn = createNewSQLQueryButton(container);
|
const newSqlQueryBtn = createNewSQLQueryButton(container);
|
||||||
buttons.push(newSqlQueryBtn);
|
buttons.push(newSqlQueryBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSupportedOpenQueryApi =
|
const isSupportedOpenQueryApi =
|
||||||
container.isPreferredApiDocumentDB() || container.isPreferredApiMongoDB() || container.isPreferredApiGraph();
|
userContext.apiType === "SQL" || container.isPreferredApiMongoDB() || userContext.apiType === "Gremlin";
|
||||||
const isSupportedOpenQueryFromDiskApi = container.isPreferredApiDocumentDB() || container.isPreferredApiGraph();
|
const isSupportedOpenQueryFromDiskApi = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
|
||||||
if (isSupportedOpenQueryApi && container.selectedNode() && container.findSelectedCollection()) {
|
if (isSupportedOpenQueryApi && container.selectedNode() && container.findSelectedCollection()) {
|
||||||
const openQueryBtn = createOpenQueryButton(container);
|
const openQueryBtn = createOpenQueryButton(container);
|
||||||
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)];
|
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton(container)];
|
||||||
@@ -107,7 +107,7 @@ export function createStaticCommandBarButtons(container: Explorer): CommandButto
|
|||||||
buttons.push(createOpenQueryFromDiskButton(container));
|
buttons.push(createOpenQueryFromDiskButton(container));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (areScriptsSupported(container)) {
|
if (areScriptsSupported()) {
|
||||||
const label = "New Stored Procedure";
|
const label = "New Stored Procedure";
|
||||||
const newStoredProcedureBtn: CommandButtonComponentProps = {
|
const newStoredProcedureBtn: CommandButtonComponentProps = {
|
||||||
iconSrc: AddStoredProcedureIcon,
|
iconSrc: AddStoredProcedureIcon,
|
||||||
@@ -154,25 +154,18 @@ export function createContextCommandBarButtons(container: Explorer): CommandButt
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createControlCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
|
export function createControlCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
|
||||||
const buttons: CommandButtonComponentProps[] = [];
|
const buttons: CommandButtonComponentProps[] = [
|
||||||
if (configContext.platform === Platform.Hosted) {
|
{
|
||||||
return buttons;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!container.isPreferredApiCassandra()) {
|
|
||||||
const label = "Settings";
|
|
||||||
const settingsPaneButton: CommandButtonComponentProps = {
|
|
||||||
iconSrc: SettingsIcon,
|
iconSrc: SettingsIcon,
|
||||||
iconAlt: label,
|
iconAlt: "Settings",
|
||||||
onCommandClick: () => container.openSettingPane(),
|
onCommandClick: () => container.openSettingPane(),
|
||||||
commandButtonLabel: undefined,
|
commandButtonLabel: undefined,
|
||||||
ariaLabel: label,
|
ariaLabel: "Settings",
|
||||||
tooltipText: label,
|
tooltipText: "Settings",
|
||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
};
|
},
|
||||||
buttons.push(settingsPaneButton);
|
];
|
||||||
}
|
|
||||||
|
|
||||||
if (container.isHostedDataExplorerEnabled()) {
|
if (container.isHostedDataExplorerEnabled()) {
|
||||||
const label = "Open Full Screen";
|
const label = "Open Full Screen";
|
||||||
@@ -223,8 +216,8 @@ export function createDivider(): CommandButtonComponentProps {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function areScriptsSupported(container: Explorer): boolean {
|
function areScriptsSupported(): boolean {
|
||||||
return container.isPreferredApiDocumentDB() || container.isPreferredApiGraph();
|
return userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNewCollectionGroup(container: Explorer): CommandButtonComponentProps {
|
function createNewCollectionGroup(container: Explorer): CommandButtonComponentProps {
|
||||||
@@ -296,7 +289,7 @@ function createNewDatabase(container: Explorer): CommandButtonComponentProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createNewSQLQueryButton(container: Explorer): CommandButtonComponentProps {
|
function createNewSQLQueryButton(container: Explorer): CommandButtonComponentProps {
|
||||||
if (container.isPreferredApiDocumentDB() || container.isPreferredApiGraph()) {
|
if (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") {
|
||||||
const label = "New SQL Query";
|
const label = "New SQL Query";
|
||||||
return {
|
return {
|
||||||
iconSrc: AddSqlQueryIcon,
|
iconSrc: AddSqlQueryIcon,
|
||||||
@@ -310,7 +303,7 @@ function createNewSQLQueryButton(container: Explorer): CommandButtonComponentPro
|
|||||||
hasPopup: true,
|
hasPopup: true,
|
||||||
disabled: container.isDatabaseNodeOrNoneSelected(),
|
disabled: container.isDatabaseNodeOrNoneSelected(),
|
||||||
};
|
};
|
||||||
} else if (container.isPreferredApiMongoDB()) {
|
} else if (userContext.apiType === "Mongo") {
|
||||||
const label = "New Query";
|
const label = "New Query";
|
||||||
return {
|
return {
|
||||||
iconSrc: AddSqlQueryIcon,
|
iconSrc: AddSqlQueryIcon,
|
||||||
@@ -332,8 +325,7 @@ function createNewSQLQueryButton(container: Explorer): CommandButtonComponentPro
|
|||||||
export function createScriptCommandButtons(container: Explorer): CommandButtonComponentProps[] {
|
export function createScriptCommandButtons(container: Explorer): CommandButtonComponentProps[] {
|
||||||
const buttons: CommandButtonComponentProps[] = [];
|
const buttons: CommandButtonComponentProps[] = [];
|
||||||
|
|
||||||
const shouldEnableScriptsCommands: boolean =
|
const shouldEnableScriptsCommands: boolean = !container.isDatabaseNodeOrNoneSelected() && areScriptsSupported();
|
||||||
!container.isDatabaseNodeOrNoneSelected() && areScriptsSupported(container);
|
|
||||||
|
|
||||||
if (shouldEnableScriptsCommands) {
|
if (shouldEnableScriptsCommands) {
|
||||||
const label = "New Stored Procedure";
|
const label = "New Stored Procedure";
|
||||||
@@ -453,7 +445,7 @@ function createEnableNotebooksButton(container: Explorer): CommandButtonComponen
|
|||||||
return {
|
return {
|
||||||
iconSrc: EnableNotebooksIcon,
|
iconSrc: EnableNotebooksIcon,
|
||||||
iconAlt: label,
|
iconAlt: label,
|
||||||
onCommandClick: () => container.setupNotebooksPane.openWithTitleAndDescription(label, description),
|
onCommandClick: () => container.openSetupNotebooksPanel(label, description),
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
hasPopup: false,
|
hasPopup: false,
|
||||||
disabled: !container.isNotebooksEnabledForAccount(),
|
disabled: !container.isNotebooksEnabledForAccount(),
|
||||||
@@ -490,7 +482,7 @@ function createOpenMongoTerminalButton(container: Explorer): CommandButtonCompon
|
|||||||
if (container.isNotebookEnabled()) {
|
if (container.isNotebookEnabled()) {
|
||||||
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
container.openNotebookTerminal(ViewModels.TerminalKind.Mongo);
|
||||||
} else {
|
} else {
|
||||||
container.setupNotebooksPane.openWithTitleAndDescription(title, description);
|
container.openSetupNotebooksPanel(title, description);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
@@ -516,7 +508,7 @@ function createOpenCassandraTerminalButton(container: Explorer): CommandButtonCo
|
|||||||
if (container.isNotebookEnabled()) {
|
if (container.isNotebookEnabled()) {
|
||||||
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
|
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
|
||||||
} else {
|
} else {
|
||||||
container.setupNotebooksPane.openWithTitleAndDescription(title, description);
|
container.openSetupNotebooksPanel(title, description);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
commandButtonLabel: label,
|
commandButtonLabel: label,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from "react";
|
|
||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
NotificationConsoleComponentProps,
|
|
||||||
NotificationConsoleComponent,
|
|
||||||
ConsoleDataType,
|
ConsoleDataType,
|
||||||
|
NotificationConsoleComponent,
|
||||||
|
NotificationConsoleComponentProps,
|
||||||
} from "./NotificationConsoleComponent";
|
} from "./NotificationConsoleComponent";
|
||||||
|
|
||||||
describe("NotificationConsoleComponent", () => {
|
describe("NotificationConsoleComponent", () => {
|
||||||
@@ -12,7 +12,7 @@ describe("NotificationConsoleComponent", () => {
|
|||||||
consoleData: undefined,
|
consoleData: undefined,
|
||||||
isConsoleExpanded: false,
|
isConsoleExpanded: false,
|
||||||
inProgressConsoleDataIdToBeDeleted: "",
|
inProgressConsoleDataIdToBeDeleted: "",
|
||||||
setIsConsoleExpanded: (isExpanded: boolean): void => {},
|
setIsConsoleExpanded: (): void => undefined,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ describe("NotificationConsoleComponent", () => {
|
|||||||
wrapper.setProps(props);
|
wrapper.setProps(props);
|
||||||
expect(wrapper.find(".notificationConsoleData .date").text()).toEqual(date);
|
expect(wrapper.find(".notificationConsoleData .date").text()).toEqual(date);
|
||||||
expect(wrapper.find(".notificationConsoleData .message").text()).toEqual(message);
|
expect(wrapper.find(".notificationConsoleData .message").text()).toEqual(message);
|
||||||
expect(wrapper.exists(`.notificationConsoleData .${iconClassName}`));
|
expect(wrapper.exists(`.notificationConsoleData .${iconClassName}`)).toBe(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
it("renders progress notifications", () => {
|
it("renders progress notifications", () => {
|
||||||
@@ -139,7 +139,7 @@ describe("NotificationConsoleComponent", () => {
|
|||||||
wrapper.setProps(props);
|
wrapper.setProps(props);
|
||||||
|
|
||||||
wrapper.find(".clearNotificationsButton").simulate("click");
|
wrapper.find(".clearNotificationsButton").simulate("click");
|
||||||
expect(!wrapper.exists(".notificationConsoleData"));
|
expect(wrapper.exists(".notificationConsoleData")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("collapses and hide content", () => {
|
it("collapses and hide content", () => {
|
||||||
@@ -155,7 +155,7 @@ describe("NotificationConsoleComponent", () => {
|
|||||||
wrapper.setProps(props);
|
wrapper.setProps(props);
|
||||||
|
|
||||||
wrapper.find(".notificationConsoleHeader").simulate("click");
|
wrapper.find(".notificationConsoleHeader").simulate("click");
|
||||||
expect(!wrapper.exists(".notificationConsoleContent"));
|
expect(wrapper.exists(".notificationConsoleContent")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("display latest data in header", () => {
|
it("display latest data in header", () => {
|
||||||
|
|||||||
@@ -2,19 +2,20 @@
|
|||||||
* React component for control bar
|
* React component for control bar
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from "react";
|
|
||||||
import { ClientDefaults, KeyCodes } from "../../../Common/Constants";
|
|
||||||
import AnimateHeight from "react-animate-height";
|
|
||||||
import { Dropdown, IDropdownOption } from "office-ui-fabric-react";
|
import { Dropdown, IDropdownOption } from "office-ui-fabric-react";
|
||||||
import LoadingIcon from "../../../../images/loading.svg";
|
import * as React from "react";
|
||||||
|
import AnimateHeight from "react-animate-height";
|
||||||
|
import LoaderIcon from "../../../../images/circular_loader_black_16x16.gif";
|
||||||
|
import ClearIcon from "../../../../images/Clear.svg";
|
||||||
import ErrorBlackIcon from "../../../../images/error_black.svg";
|
import ErrorBlackIcon from "../../../../images/error_black.svg";
|
||||||
|
import ErrorRedIcon from "../../../../images/error_red.svg";
|
||||||
import infoBubbleIcon from "../../../../images/info-bubble-9x9.svg";
|
import infoBubbleIcon from "../../../../images/info-bubble-9x9.svg";
|
||||||
import InfoIcon from "../../../../images/info_color.svg";
|
import InfoIcon from "../../../../images/info_color.svg";
|
||||||
import ErrorRedIcon from "../../../../images/error_red.svg";
|
import LoadingIcon from "../../../../images/loading.svg";
|
||||||
import ClearIcon from "../../../../images/Clear.svg";
|
|
||||||
import LoaderIcon from "../../../../images/circular_loader_black_16x16.gif";
|
|
||||||
import ChevronUpIcon from "../../../../images/QueryBuilder/CollapseChevronUp_16x.png";
|
|
||||||
import ChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
import ChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
|
||||||
|
import ChevronUpIcon from "../../../../images/QueryBuilder/CollapseChevronUp_16x.png";
|
||||||
|
import { ClientDefaults, KeyCodes } from "../../../Common/Constants";
|
||||||
|
import { userContext } from "../../../UserContext";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log levels
|
* Log levels
|
||||||
@@ -76,7 +77,7 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
public componentDidUpdate(
|
public componentDidUpdate(
|
||||||
prevProps: NotificationConsoleComponentProps,
|
prevProps: NotificationConsoleComponentProps,
|
||||||
prevState: NotificationConsoleComponentState
|
prevState: NotificationConsoleComponentState
|
||||||
) {
|
): void {
|
||||||
const currentHeaderStatus = NotificationConsoleComponent.extractHeaderStatus(this.props.consoleData);
|
const currentHeaderStatus = NotificationConsoleComponent.extractHeaderStatus(this.props.consoleData);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -97,7 +98,7 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public setElememntRef = (element: HTMLElement) => {
|
public setElememntRef = (element: HTMLElement): void => {
|
||||||
this.consoleHeaderElement = element;
|
this.consoleHeaderElement = element;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -116,7 +117,7 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
className="notificationConsoleHeader"
|
className="notificationConsoleHeader"
|
||||||
id="notificationConsoleHeader"
|
id="notificationConsoleHeader"
|
||||||
ref={this.setElememntRef}
|
ref={this.setElememntRef}
|
||||||
onClick={(event: React.MouseEvent<HTMLDivElement>) => this.expandCollapseConsole()}
|
onClick={() => this.expandCollapseConsole()}
|
||||||
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onExpandCollapseKeyPress(event)}
|
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onExpandCollapseKeyPress(event)}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
@@ -135,6 +136,7 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
<span className="numInfoItems">{numInfoItems}</span>
|
<span className="numInfoItems">{numInfoItems}</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
{userContext.features.pr && <PrPreview pr={userContext.features.pr} />}
|
||||||
<span className="consoleSplitter" />
|
<span className="consoleSplitter" />
|
||||||
<span className="headerStatus">
|
<span className="headerStatus">
|
||||||
<span className="headerStatusEllipsis">{this.state.headerStatus}</span>
|
<span className="headerStatusEllipsis">{this.state.headerStatus}</span>
|
||||||
@@ -304,3 +306,18 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PrPreview = (props: { pr: string }) => {
|
||||||
|
const url = new URL(props.pr);
|
||||||
|
const [, ref] = url.hash.split("#");
|
||||||
|
url.hash = "";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span className="consoleSplitter" />
|
||||||
|
<a target="_blank" rel="noreferrer" href={url.href} style={{ marginRight: "1em", fontWeight: "bold" }}>
|
||||||
|
{ref}
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
// Manages all the redux logic for the notebook nteract code
|
// Manages all the redux logic for the notebook nteract code
|
||||||
// TODO: Merge with NotebookClient?
|
// TODO: Merge with NotebookClient?
|
||||||
import { NotebookWorkspaceConnectionInfo } from "../../Contracts/DataModels";
|
|
||||||
import * as Constants from "../../Common/Constants";
|
|
||||||
import { CdbAppState, makeCdbRecord } from "./NotebookComponent/types";
|
|
||||||
|
|
||||||
// Vendor modules
|
// Vendor modules
|
||||||
import {
|
import {
|
||||||
actions,
|
actions,
|
||||||
AppState,
|
AppState,
|
||||||
|
ContentRecord,
|
||||||
createHostRef,
|
createHostRef,
|
||||||
createKernelspecsRef,
|
createKernelspecsRef,
|
||||||
|
HostRecord,
|
||||||
|
HostRef,
|
||||||
|
IContentProvider,
|
||||||
|
KernelspecsRef,
|
||||||
makeAppRecord,
|
makeAppRecord,
|
||||||
makeCommsRecord,
|
makeCommsRecord,
|
||||||
makeContentsRecord,
|
makeContentsRecord,
|
||||||
@@ -19,23 +20,20 @@ import {
|
|||||||
makeJupyterHostRecord,
|
makeJupyterHostRecord,
|
||||||
makeStateRecord,
|
makeStateRecord,
|
||||||
makeTransformsRecord,
|
makeTransformsRecord,
|
||||||
ContentRecord,
|
|
||||||
HostRecord,
|
|
||||||
HostRef,
|
|
||||||
KernelspecsRef,
|
|
||||||
IContentProvider,
|
|
||||||
} from "@nteract/core";
|
} from "@nteract/core";
|
||||||
|
import { configOption, createConfigCollection, defineConfigOption } from "@nteract/mythic-configuration";
|
||||||
import { Media } from "@nteract/outputs";
|
import { Media } from "@nteract/outputs";
|
||||||
import TransformVDOM from "@nteract/transform-vdom";
|
import TransformVDOM from "@nteract/transform-vdom";
|
||||||
import * as Immutable from "immutable";
|
import * as Immutable from "immutable";
|
||||||
import { Store, AnyAction, MiddlewareAPI, Middleware, Dispatch } from "redux";
|
|
||||||
|
|
||||||
import configureStore from "./NotebookComponent/store";
|
|
||||||
|
|
||||||
import { Notification } from "react-notification-system";
|
import { Notification } from "react-notification-system";
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
import { AnyAction, Dispatch, Middleware, MiddlewareAPI, Store } from "redux";
|
||||||
|
import * as Constants from "../../Common/Constants";
|
||||||
|
import { NotebookWorkspaceConnectionInfo } from "../../Contracts/DataModels";
|
||||||
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { configOption, createConfigCollection, defineConfigOption } from "@nteract/mythic-configuration";
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
import configureStore from "./NotebookComponent/store";
|
||||||
|
import { CdbAppState, makeCdbRecord } from "./NotebookComponent/types";
|
||||||
|
|
||||||
export type KernelSpecsDisplay = { name: string; displayName: string };
|
export type KernelSpecsDisplay = { name: string; displayName: string };
|
||||||
|
|
||||||
@@ -125,60 +123,62 @@ export class NotebookClientV2 {
|
|||||||
contents: makeContentsRecord({
|
contents: makeContentsRecord({
|
||||||
byRef: Immutable.Map<string, ContentRecord>(),
|
byRef: Immutable.Map<string, ContentRecord>(),
|
||||||
}),
|
}),
|
||||||
transforms: makeTransformsRecord({
|
transforms: userContext.features.sandboxNotebookOutputs
|
||||||
displayOrder: Immutable.List([
|
? undefined
|
||||||
"application/vnd.jupyter.widget-view+json",
|
: makeTransformsRecord({
|
||||||
"application/vnd.vega.v5+json",
|
displayOrder: Immutable.List([
|
||||||
"application/vnd.vega.v4+json",
|
"application/vnd.jupyter.widget-view+json",
|
||||||
"application/vnd.vega.v3+json",
|
"application/vnd.vega.v5+json",
|
||||||
"application/vnd.vega.v2+json",
|
"application/vnd.vega.v4+json",
|
||||||
"application/vnd.vegalite.v3+json",
|
"application/vnd.vega.v3+json",
|
||||||
"application/vnd.vegalite.v2+json",
|
"application/vnd.vega.v2+json",
|
||||||
"application/vnd.vegalite.v1+json",
|
"application/vnd.vegalite.v3+json",
|
||||||
"application/geo+json",
|
"application/vnd.vegalite.v2+json",
|
||||||
"application/vnd.plotly.v1+json",
|
"application/vnd.vegalite.v1+json",
|
||||||
"text/vnd.plotly.v1+html",
|
"application/geo+json",
|
||||||
"application/x-nteract-model-debug+json",
|
"application/vnd.plotly.v1+json",
|
||||||
"application/vnd.dataresource+json",
|
"text/vnd.plotly.v1+html",
|
||||||
"application/vdom.v1+json",
|
"application/x-nteract-model-debug+json",
|
||||||
"application/json",
|
"application/vnd.dataresource+json",
|
||||||
"application/javascript",
|
"application/vdom.v1+json",
|
||||||
"text/html",
|
"application/json",
|
||||||
"text/markdown",
|
"application/javascript",
|
||||||
"text/latex",
|
"text/html",
|
||||||
"image/svg+xml",
|
"text/markdown",
|
||||||
"image/gif",
|
"text/latex",
|
||||||
"image/png",
|
"image/svg+xml",
|
||||||
"image/jpeg",
|
"image/gif",
|
||||||
"text/plain",
|
"image/png",
|
||||||
]),
|
"image/jpeg",
|
||||||
byId: Immutable.Map({
|
"text/plain",
|
||||||
"text/vnd.plotly.v1+html": NullTransform,
|
]),
|
||||||
"application/vnd.plotly.v1+json": NullTransform,
|
byId: Immutable.Map({
|
||||||
"application/geo+json": NullTransform,
|
"text/vnd.plotly.v1+html": NullTransform,
|
||||||
"application/x-nteract-model-debug+json": NullTransform,
|
"application/vnd.plotly.v1+json": NullTransform,
|
||||||
"application/vnd.dataresource+json": NullTransform,
|
"application/geo+json": NullTransform,
|
||||||
"application/vnd.jupyter.widget-view+json": NullTransform,
|
"application/x-nteract-model-debug+json": NullTransform,
|
||||||
"application/vnd.vegalite.v1+json": NullTransform,
|
"application/vnd.dataresource+json": NullTransform,
|
||||||
"application/vnd.vegalite.v2+json": NullTransform,
|
"application/vnd.jupyter.widget-view+json": NullTransform,
|
||||||
"application/vnd.vegalite.v3+json": NullTransform,
|
"application/vnd.vegalite.v1+json": NullTransform,
|
||||||
"application/vnd.vega.v2+json": NullTransform,
|
"application/vnd.vegalite.v2+json": NullTransform,
|
||||||
"application/vnd.vega.v3+json": NullTransform,
|
"application/vnd.vegalite.v3+json": NullTransform,
|
||||||
"application/vnd.vega.v4+json": NullTransform,
|
"application/vnd.vega.v2+json": NullTransform,
|
||||||
"application/vnd.vega.v5+json": NullTransform,
|
"application/vnd.vega.v3+json": NullTransform,
|
||||||
"application/vdom.v1+json": TransformVDOM,
|
"application/vnd.vega.v4+json": NullTransform,
|
||||||
"application/json": Media.Json,
|
"application/vnd.vega.v5+json": NullTransform,
|
||||||
"application/javascript": Media.JavaScript,
|
"application/vdom.v1+json": TransformVDOM,
|
||||||
"text/html": Media.HTML,
|
"application/json": Media.Json,
|
||||||
"text/markdown": Media.Markdown,
|
"application/javascript": Media.JavaScript,
|
||||||
"text/latex": Media.LaTeX,
|
"text/html": Media.HTML,
|
||||||
"image/svg+xml": Media.SVG,
|
"text/markdown": Media.Markdown,
|
||||||
"image/gif": Media.Image,
|
"text/latex": Media.LaTeX,
|
||||||
"image/png": Media.Image,
|
"image/svg+xml": Media.SVG,
|
||||||
"image/jpeg": Media.Image,
|
"image/gif": Media.Image,
|
||||||
"text/plain": Media.Plain,
|
"image/png": Media.Image,
|
||||||
}),
|
"image/jpeg": Media.Image,
|
||||||
}),
|
"text/plain": Media.Plain,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
cdb: makeCdbRecord({
|
cdb: makeCdbRecord({
|
||||||
@@ -200,10 +200,11 @@ export class NotebookClientV2 {
|
|||||||
case actions.FETCH_KERNELSPECS_FULFILLED: {
|
case actions.FETCH_KERNELSPECS_FULFILLED: {
|
||||||
const payload = ((action as unknown) as actions.FetchKernelspecsFulfilled).payload;
|
const payload = ((action as unknown) as actions.FetchKernelspecsFulfilled).payload;
|
||||||
const defaultKernelName = payload.defaultKernelName;
|
const defaultKernelName = payload.defaultKernelName;
|
||||||
this.kernelSpecsForDisplay = Object.keys(payload.kernelspecs)
|
this.kernelSpecsForDisplay = Object.values(payload.kernelspecs)
|
||||||
.map((name) => ({
|
.filter((spec) => !spec.metadata?.hasOwnProperty("hidden"))
|
||||||
name,
|
.map((spec) => ({
|
||||||
displayName: payload.kernelspecs[name].displayName,
|
name: spec.name,
|
||||||
|
displayName: spec.displayName,
|
||||||
}))
|
}))
|
||||||
.sort((a: KernelSpecsDisplay, b: KernelSpecsDisplay) => {
|
.sort((a: KernelSpecsDisplay, b: KernelSpecsDisplay) => {
|
||||||
// Put default at the top, otherwise lexicographically compare
|
// Put default at the top, otherwise lexicographically compare
|
||||||
|
|||||||
@@ -1,52 +1,49 @@
|
|||||||
import { EMPTY, merge, of, timer, concat, Subject, Subscriber, Observable, Observer, from } from "rxjs";
|
|
||||||
import { webSocket } from "rxjs/webSocket";
|
|
||||||
import { StateObservable } from "redux-observable";
|
|
||||||
import { ofType } from "redux-observable";
|
|
||||||
import {
|
import {
|
||||||
mergeMap,
|
|
||||||
tap,
|
|
||||||
retryWhen,
|
|
||||||
delayWhen,
|
|
||||||
map,
|
|
||||||
switchMap,
|
|
||||||
take,
|
|
||||||
filter,
|
|
||||||
catchError,
|
|
||||||
first,
|
|
||||||
concatMap,
|
|
||||||
timeout,
|
|
||||||
} from "rxjs/operators";
|
|
||||||
import {
|
|
||||||
AppState,
|
|
||||||
ServerConfig as JupyterServerConfig,
|
|
||||||
JupyterHostRecordProps,
|
|
||||||
RemoteKernelProps,
|
|
||||||
castToSessionId,
|
|
||||||
createKernelRef,
|
|
||||||
KernelRef,
|
|
||||||
ContentRef,
|
|
||||||
KernelInfo,
|
|
||||||
actions,
|
actions,
|
||||||
|
AppState,
|
||||||
|
castToSessionId,
|
||||||
|
ContentRef,
|
||||||
|
createKernelRef,
|
||||||
|
JupyterHostRecordProps,
|
||||||
|
KernelInfo,
|
||||||
|
KernelRef,
|
||||||
|
RemoteKernelProps,
|
||||||
selectors,
|
selectors,
|
||||||
|
ServerConfig as JupyterServerConfig,
|
||||||
} from "@nteract/core";
|
} from "@nteract/core";
|
||||||
import { message, JupyterMessage, Channels, createMessage, childOf, ofMessageType } from "@nteract/messaging";
|
import { Channels, childOf, createMessage, JupyterMessage, message, ofMessageType } from "@nteract/messaging";
|
||||||
import { sessions, kernels } from "rx-jupyter";
|
|
||||||
import { RecordOf } from "immutable";
|
import { RecordOf } from "immutable";
|
||||||
import { AnyAction } from "redux";
|
import { AnyAction } from "redux";
|
||||||
|
import { ofType, StateObservable } from "redux-observable";
|
||||||
|
import { kernels, sessions } from "rx-jupyter";
|
||||||
|
import { concat, EMPTY, from, merge, Observable, Observer, of, Subject, Subscriber, timer } from "rxjs";
|
||||||
|
import {
|
||||||
|
catchError,
|
||||||
|
concatMap,
|
||||||
|
delayWhen,
|
||||||
|
filter,
|
||||||
|
first,
|
||||||
|
map,
|
||||||
|
mergeMap,
|
||||||
|
retryWhen,
|
||||||
|
switchMap,
|
||||||
|
take,
|
||||||
|
tap,
|
||||||
|
timeout,
|
||||||
|
} from "rxjs/operators";
|
||||||
|
import { webSocket } from "rxjs/webSocket";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import { Areas } from "../../../Common/Constants";
|
||||||
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import * as CdbActions from "./actions";
|
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { Action as TelemetryAction, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action as TelemetryAction, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import { CdbAppState } from "./types";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { decryptJWTToken } from "../../../Utils/AuthorizationUtils";
|
import { decryptJWTToken } from "../../../Utils/AuthorizationUtils";
|
||||||
import * as TextFile from "./contents/file/text-file";
|
import { logConsoleError, logConsoleInfo } from "../../../Utils/NotificationConsoleUtils";
|
||||||
import { NotebookUtil } from "../NotebookUtil";
|
|
||||||
import * as FileSystemUtil from "../FileSystemUtil";
|
import * as FileSystemUtil from "../FileSystemUtil";
|
||||||
import * as cdbActions from "../NotebookComponent/actions";
|
import * as cdbActions from "../NotebookComponent/actions";
|
||||||
import { Areas } from "../../../Common/Constants";
|
import { NotebookUtil } from "../NotebookUtil";
|
||||||
|
import * as CdbActions from "./actions";
|
||||||
|
import * as TextFile from "./contents/file/text-file";
|
||||||
|
import { CdbAppState } from "./types";
|
||||||
|
|
||||||
interface NotebookServiceConfig extends JupyterServerConfig {
|
interface NotebookServiceConfig extends JupyterServerConfig {
|
||||||
userPuid?: string;
|
userPuid?: string;
|
||||||
@@ -311,7 +308,7 @@ export const launchWebSocketKernelEpic = (
|
|||||||
if (currentKernelspecs) {
|
if (currentKernelspecs) {
|
||||||
kernelSpecToLaunch = currentKernelspecs.defaultKernelName;
|
kernelSpecToLaunch = currentKernelspecs.defaultKernelName;
|
||||||
const msg = `No kernelspec name specified to launch, using default kernel: ${kernelSpecToLaunch}`;
|
const msg = `No kernelspec name specified to launch, using default kernel: ${kernelSpecToLaunch}`;
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
|
logConsoleInfo(msg);
|
||||||
logFailureToTelemetry(state$.value, "Launching alternate kernel", msg);
|
logFailureToTelemetry(state$.value, "Launching alternate kernel", msg);
|
||||||
} else {
|
} else {
|
||||||
return of(
|
return of(
|
||||||
@@ -337,7 +334,7 @@ export const launchWebSocketKernelEpic = (
|
|||||||
kernelSpecToLaunch = currentKernelspecs.defaultKernelName;
|
kernelSpecToLaunch = currentKernelspecs.defaultKernelName;
|
||||||
msg += ` Using default kernel: ${kernelSpecToLaunch}`;
|
msg += ` Using default kernel: ${kernelSpecToLaunch}`;
|
||||||
}
|
}
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
|
logConsoleInfo(msg);
|
||||||
logFailureToTelemetry(state$.value, "Launching alternate kernel", msg);
|
logFailureToTelemetry(state$.value, "Launching alternate kernel", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,7 +631,7 @@ const notificationsToUserEpic = (action$: Observable<any>, state$: StateObservab
|
|||||||
case actions.RESTART_KERNEL_SUCCESSFUL: {
|
case actions.RESTART_KERNEL_SUCCESSFUL: {
|
||||||
const title = "Kernel restart";
|
const title = "Kernel restart";
|
||||||
const msg = "Kernel successfully restarted";
|
const msg = "Kernel successfully restarted";
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, msg);
|
logConsoleInfo(msg);
|
||||||
logFailureToTelemetry(state$.value, title, msg);
|
logFailureToTelemetry(state$.value, title, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -645,7 +642,7 @@ const notificationsToUserEpic = (action$: Observable<any>, state$: StateObservab
|
|||||||
case actions.SAVE_FAILED: {
|
case actions.SAVE_FAILED: {
|
||||||
const title = "Save failure";
|
const title = "Save failure";
|
||||||
const msg = `Failed to save notebook: ${(action as actions.SaveFailed).payload.error}`;
|
const msg = `Failed to save notebook: ${(action as actions.SaveFailed).payload.error}`;
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
logConsoleError(msg);
|
||||||
logFailureToTelemetry(state$.value, title, msg);
|
logFailureToTelemetry(state$.value, title, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -654,7 +651,7 @@ const notificationsToUserEpic = (action$: Observable<any>, state$: StateObservab
|
|||||||
const filepath = selectors.filepath(state$.value, { contentRef: typedAction.payload.contentRef });
|
const filepath = selectors.filepath(state$.value, { contentRef: typedAction.payload.contentRef });
|
||||||
const title = "Fetching content failure";
|
const title = "Fetching content failure";
|
||||||
const msg = `Failed to fetch notebook content: ${filepath}, error: ${typedAction.payload.error}`;
|
const msg = `Failed to fetch notebook content: ${filepath}, error: ${typedAction.payload.error}`;
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
logConsoleError(msg);
|
||||||
logFailureToTelemetry(state$.value, title, msg);
|
logFailureToTelemetry(state$.value, title, msg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -679,7 +676,7 @@ const handleKernelConnectionLostEpic = (
|
|||||||
const state = state$.value;
|
const state = state$.value;
|
||||||
|
|
||||||
const msg = "Notebook was disconnected from kernel";
|
const msg = "Notebook was disconnected from kernel";
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
logConsoleError(msg);
|
||||||
logFailureToTelemetry(state, "Error", "Kernel connection error");
|
logFailureToTelemetry(state, "Error", "Kernel connection error");
|
||||||
|
|
||||||
const host = selectors.currentHost(state);
|
const host = selectors.currentHost(state);
|
||||||
@@ -692,7 +689,7 @@ const handleKernelConnectionLostEpic = (
|
|||||||
if (delayMs > Constants.Notebook.kernelRestartMaxDelayMs) {
|
if (delayMs > Constants.Notebook.kernelRestartMaxDelayMs) {
|
||||||
const msg =
|
const msg =
|
||||||
"Restarted kernel too many times. Please reload the page to enable Data Explorer to restart the kernel automatically.";
|
"Restarted kernel too many times. Please reload the page to enable Data Explorer to restart the kernel automatically.";
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
logConsoleError(msg);
|
||||||
logFailureToTelemetry(state, "Kernel restart error", msg);
|
logFailureToTelemetry(state, "Kernel restart error", msg);
|
||||||
|
|
||||||
const explorer = window.dataExplorer;
|
const explorer = window.dataExplorer;
|
||||||
@@ -810,7 +807,7 @@ const closeUnsupportedMimetypesEpic = (
|
|||||||
);
|
);
|
||||||
const msg = `${filepath} cannot be rendered. Please download the file, in order to view it outside of Data Explorer.`;
|
const msg = `${filepath} cannot be rendered. Please download the file, in order to view it outside of Data Explorer.`;
|
||||||
explorer.showOkModalDialog("File cannot be rendered", msg);
|
explorer.showOkModalDialog("File cannot be rendered", msg);
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
logConsoleError(msg);
|
||||||
}
|
}
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
})
|
})
|
||||||
@@ -838,7 +835,7 @@ const closeContentFailedToFetchEpic = (
|
|||||||
);
|
);
|
||||||
const msg = `Failed to load file: ${filepath}.`;
|
const msg = `Failed to load file: ${filepath}.`;
|
||||||
explorer.showOkModalDialog("Failure to load", msg);
|
explorer.showOkModalDialog("Failure to load", msg);
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
logConsoleError(msg);
|
||||||
}
|
}
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* Notebook container related stuff
|
* Notebook container related stuff
|
||||||
*/
|
*/
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import * as Logger from "../../Common/Logger";
|
|
||||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import * as Logger from "../../Common/Logger";
|
||||||
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
|
||||||
export class NotebookContainerClient {
|
export class NotebookContainerClient {
|
||||||
private reconnectingNotificationId: string;
|
private clearReconnectionAttemptMessage? = () => {};
|
||||||
private isResettingWorkspace: boolean;
|
private isResettingWorkspace: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -61,9 +60,9 @@ export class NotebookContainerClient {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
if (this.reconnectingNotificationId) {
|
if (this.clearReconnectionAttemptMessage) {
|
||||||
NotificationConsoleUtils.clearInProgressMessageWithId(this.reconnectingNotificationId);
|
this.clearReconnectionAttemptMessage();
|
||||||
this.reconnectingNotificationId = "";
|
this.clearReconnectionAttemptMessage = undefined;
|
||||||
}
|
}
|
||||||
const memoryUsageInfo = await response.json();
|
const memoryUsageInfo = await response.json();
|
||||||
if (memoryUsageInfo) {
|
if (memoryUsageInfo) {
|
||||||
@@ -76,9 +75,8 @@ export class NotebookContainerClient {
|
|||||||
return undefined;
|
return undefined;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.logError(getErrorMessage(error), "NotebookContainerClient/getMemoryUsage");
|
Logger.logError(getErrorMessage(error), "NotebookContainerClient/getMemoryUsage");
|
||||||
if (!this.reconnectingNotificationId) {
|
if (!this.clearReconnectionAttemptMessage) {
|
||||||
this.reconnectingNotificationId = NotificationConsoleUtils.logConsoleMessage(
|
this.clearReconnectionAttemptMessage = logConsoleProgress(
|
||||||
ConsoleDataType.InProgress,
|
|
||||||
"Connection lost with Notebook server. Attempting to reconnect..."
|
"Connection lost with Notebook server. Attempting to reconnect..."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,30 +2,31 @@
|
|||||||
* Contains all notebook related stuff meant to be dynamically loaded by explorer
|
* Contains all notebook related stuff meant to be dynamically loaded by explorer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { JunoClient } from "../../Juno/JunoClient";
|
|
||||||
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
|
|
||||||
import { GitHubClient } from "../../GitHub/GitHubClient";
|
|
||||||
import * as Logger from "../../Common/Logger";
|
|
||||||
import { HttpStatusCodes, Areas } from "../../Common/Constants";
|
|
||||||
import { GitHubReposPane } from "../Panes/GitHubReposPane";
|
|
||||||
import ko from "knockout";
|
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import { IContentProvider } from "@nteract/core";
|
|
||||||
import { NotebookContentProvider } from "./NotebookComponent/NotebookContentProvider";
|
|
||||||
import { GitHubContentProvider } from "../../GitHub/GitHubContentProvider";
|
|
||||||
import { contents } from "rx-jupyter";
|
|
||||||
import { NotebookContainerClient } from "./NotebookContainerClient";
|
|
||||||
import { MemoryUsageInfo } from "../../Contracts/DataModels";
|
|
||||||
import { NotebookContentClient } from "./NotebookContentClient";
|
|
||||||
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
|
||||||
import { PublishNotebookPaneAdapter } from "../Panes/PublishNotebookPaneAdapter";
|
|
||||||
import { getFullName } from "../../Utils/UserUtils";
|
|
||||||
import { ImmutableNotebook } from "@nteract/commutable";
|
import { ImmutableNotebook } from "@nteract/commutable";
|
||||||
|
import { IContentProvider } from "@nteract/core";
|
||||||
|
import ko from "knockout";
|
||||||
|
import React from "react";
|
||||||
|
import { contents } from "rx-jupyter";
|
||||||
|
import { Areas, HttpStatusCodes } from "../../Common/Constants";
|
||||||
|
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import * as Logger from "../../Common/Logger";
|
||||||
|
import { MemoryUsageInfo } from "../../Contracts/DataModels";
|
||||||
|
import { GitHubClient } from "../../GitHub/GitHubClient";
|
||||||
|
import { GitHubContentProvider } from "../../GitHub/GitHubContentProvider";
|
||||||
|
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
|
||||||
|
import { JunoClient } from "../../Juno/JunoClient";
|
||||||
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { getFullName } from "../../Utils/UserUtils";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
import { ContextualPaneBase } from "../Panes/ContextualPaneBase";
|
import { ContextualPaneBase } from "../Panes/ContextualPaneBase";
|
||||||
import { CopyNotebookPaneAdapter } from "../Panes/CopyNotebookPane";
|
import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane";
|
||||||
import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
import { GitHubReposPane } from "../Panes/GitHubReposPane";
|
||||||
|
import { PublishNotebookPaneAdapter } from "../Panes/PublishNotebookPaneAdapter";
|
||||||
|
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
||||||
|
import { NotebookContentProvider } from "./NotebookComponent/NotebookContentProvider";
|
||||||
|
import { NotebookContainerClient } from "./NotebookContainerClient";
|
||||||
|
import { NotebookContentClient } from "./NotebookContentClient";
|
||||||
|
|
||||||
export interface NotebookManagerOptions {
|
export interface NotebookManagerOptions {
|
||||||
container: Explorer;
|
container: Explorer;
|
||||||
@@ -49,7 +50,6 @@ export default class NotebookManager {
|
|||||||
|
|
||||||
public gitHubReposPane: ContextualPaneBase;
|
public gitHubReposPane: ContextualPaneBase;
|
||||||
public publishNotebookPaneAdapter: PublishNotebookPaneAdapter;
|
public publishNotebookPaneAdapter: PublishNotebookPaneAdapter;
|
||||||
public copyNotebookPaneAdapter: CopyNotebookPaneAdapter;
|
|
||||||
|
|
||||||
public initialize(params: NotebookManagerOptions): void {
|
public initialize(params: NotebookManagerOptions): void {
|
||||||
this.params = params;
|
this.params = params;
|
||||||
@@ -89,12 +89,6 @@ export default class NotebookManager {
|
|||||||
|
|
||||||
this.publishNotebookPaneAdapter = new PublishNotebookPaneAdapter(this.params.container, this.junoClient);
|
this.publishNotebookPaneAdapter = new PublishNotebookPaneAdapter(this.params.container, this.junoClient);
|
||||||
|
|
||||||
this.copyNotebookPaneAdapter = new CopyNotebookPaneAdapter(
|
|
||||||
this.params.container,
|
|
||||||
this.junoClient,
|
|
||||||
this.gitHubOAuthService
|
|
||||||
);
|
|
||||||
|
|
||||||
this.gitHubOAuthService.getTokenObservable().subscribe((token) => {
|
this.gitHubOAuthService.getTokenObservable().subscribe((token) => {
|
||||||
this.gitHubClient.setToken(token?.access_token);
|
this.gitHubClient.setToken(token?.access_token);
|
||||||
|
|
||||||
@@ -129,7 +123,18 @@ export default class NotebookManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public openCopyNotebookPane(name: string, content: string): void {
|
public openCopyNotebookPane(name: string, content: string): void {
|
||||||
this.copyNotebookPaneAdapter.open(name, content);
|
const { container } = this.params;
|
||||||
|
container.openSidePanel(
|
||||||
|
"Copy Notebook",
|
||||||
|
<CopyNotebookPane
|
||||||
|
container={container}
|
||||||
|
closePanel={container.closeSidePanel}
|
||||||
|
junoClient={this.junoClient}
|
||||||
|
gitHubOAuthService={this.gitHubOAuthService}
|
||||||
|
name={name}
|
||||||
|
content={content}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Octokit's error handler uses any
|
// Octokit's error handler uses any
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
import * as React from "react";
|
|
||||||
import "./base.css";
|
|
||||||
import "./default.css";
|
|
||||||
|
|
||||||
import { CodeCell, RawCell, Cells, MarkdownCell } from "@nteract/stateful-components";
|
|
||||||
import Prompt, { PassedPromptProps } from "@nteract/stateful-components/lib/inputs/prompt";
|
|
||||||
import { AzureTheme } from "./AzureTheme";
|
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { Dispatch } from "redux";
|
|
||||||
import { actions, ContentRef } from "@nteract/core";
|
import { actions, ContentRef } from "@nteract/core";
|
||||||
import loadTransform from "../NotebookComponent/loadTransform";
|
import { Cells, CodeCell, MarkdownCell, RawCell } from "@nteract/stateful-components";
|
||||||
import MonacoEditor from "@nteract/stateful-components/lib/inputs/connected-editors/monacoEditor";
|
import MonacoEditor from "@nteract/stateful-components/lib/inputs/connected-editors/monacoEditor";
|
||||||
import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor";
|
import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor";
|
||||||
|
import Prompt, { PassedPromptProps } from "@nteract/stateful-components/lib/inputs/prompt";
|
||||||
|
import * as React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Dispatch } from "redux";
|
||||||
|
import { userContext } from "../../../UserContext";
|
||||||
|
import loadTransform from "../NotebookComponent/loadTransform";
|
||||||
|
import { AzureTheme } from "./AzureTheme";
|
||||||
|
import "./base.css";
|
||||||
|
import "./default.css";
|
||||||
import "./NotebookReadOnlyRenderer.less";
|
import "./NotebookReadOnlyRenderer.less";
|
||||||
|
import SandboxOutputs from "./outputs/SandboxOutputs";
|
||||||
|
|
||||||
export interface NotebookRendererProps {
|
export interface NotebookRendererProps {
|
||||||
contentRef: any;
|
contentRef: any;
|
||||||
@@ -25,7 +25,9 @@ export interface NotebookRendererProps {
|
|||||||
*/
|
*/
|
||||||
class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
loadTransform(this.props as any);
|
if (!userContext.features.sandboxNotebookOutputs) {
|
||||||
|
loadTransform(this.props as any);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderPrompt(id: string, contentRef: string): JSX.Element {
|
private renderPrompt(id: string, contentRef: string): JSX.Element {
|
||||||
@@ -60,6 +62,9 @@ class NotebookReadOnlyRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
<CodeCell id={id} contentRef={contentRef}>
|
<CodeCell id={id} contentRef={contentRef}>
|
||||||
{{
|
{{
|
||||||
prompt: (props: { id: string; contentRef: string }) => this.renderPrompt(props.id, props.contentRef),
|
prompt: (props: { id: string; contentRef: string }) => this.renderPrompt(props.id, props.contentRef),
|
||||||
|
outputs: userContext.features.sandboxNotebookOutputs
|
||||||
|
? () => <SandboxOutputs id={id} contentRef={contentRef} />
|
||||||
|
: undefined,
|
||||||
editor: {
|
editor: {
|
||||||
monaco: (props: PassedEditorProps) =>
|
monaco: (props: PassedEditorProps) =>
|
||||||
this.props.hideInputs ? <></> : <MonacoEditor readOnly={true} {...props} editorType={"monaco"} />,
|
this.props.hideInputs ? <></> : <MonacoEditor readOnly={true} {...props} editorType={"monaco"} />,
|
||||||
|
|||||||
@@ -1,37 +1,31 @@
|
|||||||
import * as React from "react";
|
import { CellId } from "@nteract/commutable";
|
||||||
import "./base.css";
|
import { CellType } from "@nteract/commutable/src";
|
||||||
import "./default.css";
|
import { actions, ContentRef } from "@nteract/core";
|
||||||
|
import { Cells, CodeCell, RawCell } from "@nteract/stateful-components";
|
||||||
import { RawCell, Cells, CodeCell, MarkdownCell } from "@nteract/stateful-components";
|
|
||||||
import MonacoEditor from "@nteract/stateful-components/lib/inputs/connected-editors/monacoEditor";
|
import MonacoEditor from "@nteract/stateful-components/lib/inputs/connected-editors/monacoEditor";
|
||||||
import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor";
|
import { PassedEditorProps } from "@nteract/stateful-components/lib/inputs/editor";
|
||||||
|
import * as React from "react";
|
||||||
import Prompt from "./Prompt";
|
|
||||||
import { promptContent } from "./PromptContent";
|
|
||||||
|
|
||||||
import { AzureTheme } from "./AzureTheme";
|
|
||||||
import { DndProvider } from "react-dnd";
|
import { DndProvider } from "react-dnd";
|
||||||
import HTML5Backend from "react-dnd-html5-backend";
|
import HTML5Backend from "react-dnd-html5-backend";
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Dispatch } from "redux";
|
import { Dispatch } from "redux";
|
||||||
import { actions, ContentRef } from "@nteract/core";
|
import { userContext } from "../../../UserContext";
|
||||||
import { CellId } from "@nteract/commutable";
|
|
||||||
import loadTransform from "../NotebookComponent/loadTransform";
|
|
||||||
import DraggableCell from "./decorators/draggable";
|
|
||||||
import CellCreator from "./decorators/CellCreator";
|
|
||||||
import KeyboardShortcuts from "./decorators/kbd-shortcuts";
|
|
||||||
|
|
||||||
import CellToolbar from "./Toolbar";
|
|
||||||
import StatusBar from "./StatusBar";
|
|
||||||
|
|
||||||
import HijackScroll from "./decorators/hijack-scroll";
|
|
||||||
import { CellType } from "@nteract/commutable/src";
|
|
||||||
|
|
||||||
import "./NotebookRenderer.less";
|
|
||||||
import HoverableCell from "./decorators/HoverableCell";
|
|
||||||
import CellLabeler from "./decorators/CellLabeler";
|
|
||||||
import * as cdbActions from "../NotebookComponent/actions";
|
import * as cdbActions from "../NotebookComponent/actions";
|
||||||
|
import loadTransform from "../NotebookComponent/loadTransform";
|
||||||
|
import { AzureTheme } from "./AzureTheme";
|
||||||
|
import "./base.css";
|
||||||
|
import CellCreator from "./decorators/CellCreator";
|
||||||
|
import CellLabeler from "./decorators/CellLabeler";
|
||||||
|
import HoverableCell from "./decorators/HoverableCell";
|
||||||
|
import KeyboardShortcuts from "./decorators/kbd-shortcuts";
|
||||||
|
import "./default.css";
|
||||||
|
import MarkdownCell from "./markdown-cell";
|
||||||
|
import "./NotebookRenderer.less";
|
||||||
|
import SandboxOutputs from "./outputs/SandboxOutputs";
|
||||||
|
import Prompt from "./Prompt";
|
||||||
|
import { promptContent } from "./PromptContent";
|
||||||
|
import StatusBar from "./StatusBar";
|
||||||
|
import CellToolbar from "./Toolbar";
|
||||||
|
|
||||||
export interface NotebookRendererBaseProps {
|
export interface NotebookRendererBaseProps {
|
||||||
contentRef: any;
|
contentRef: any;
|
||||||
@@ -75,7 +69,9 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
loadTransform(this.props as any);
|
if (!userContext.features.sandboxNotebookOutputs) {
|
||||||
|
loadTransform(this.props as any);
|
||||||
|
}
|
||||||
this.props.updateNotebookParentDomElt(this.props.contentRef, this.notebookRendererRef.current);
|
this.props.updateNotebookParentDomElt(this.props.contentRef, this.notebookRendererRef.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +108,9 @@ class BaseNotebookRenderer extends React.Component<NotebookRendererProps> {
|
|||||||
</Prompt>
|
</Prompt>
|
||||||
),
|
),
|
||||||
toolbar: () => <CellToolbar id={id} contentRef={contentRef} />,
|
toolbar: () => <CellToolbar id={id} contentRef={contentRef} />,
|
||||||
|
outputs: userContext.features.sandboxNotebookOutputs
|
||||||
|
? () => <SandboxOutputs id={id} contentRef={contentRef} />
|
||||||
|
: undefined,
|
||||||
}}
|
}}
|
||||||
</CodeCell>
|
</CodeCell>
|
||||||
),
|
),
|
||||||
|
|||||||
160
src/Explorer/Notebook/NotebookRenderer/markdown-cell.tsx
Normal file
160
src/Explorer/Notebook/NotebookRenderer/markdown-cell.tsx
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
// TODO The purpose of importing this source file https://github.com/nteract/nteract/blob/main/packages/stateful-components/src/cells/markdown-cell.tsx
|
||||||
|
// into our source is to be able to overwrite the version of react-markdown which has this fix ("escape html to false")
|
||||||
|
// https://github.com/nteract/markdown/commit/e19c7cc590a4379fc507f67a7b4228363b9d8631 without having to upgrade
|
||||||
|
// @nteract/stateful-component which causes runtime issues.
|
||||||
|
|
||||||
|
import { ImmutableCell } from "@nteract/commutable/src";
|
||||||
|
import { actions, AppState, ContentRef, selectors } from "@nteract/core";
|
||||||
|
import { MarkdownPreviewer } from "@nteract/markdown";
|
||||||
|
import { defineConfigOption } from "@nteract/mythic-configuration";
|
||||||
|
import { Source as BareSource } from "@nteract/presentational-components";
|
||||||
|
import Editor, { EditorSlots } from "@nteract/stateful-components/lib/inputs/editor";
|
||||||
|
import React from "react";
|
||||||
|
import { ReactMarkdownProps } from "react-markdown";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Dispatch } from "redux";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
const { selector: markdownConfig } = defineConfigOption({
|
||||||
|
key: "markdownOptions",
|
||||||
|
label: "Markdown Editor Options",
|
||||||
|
defaultValue: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface NamedMDCellSlots {
|
||||||
|
editor?: EditorSlots;
|
||||||
|
toolbar?: () => JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ComponentProps {
|
||||||
|
id: string;
|
||||||
|
contentRef: ContentRef;
|
||||||
|
cell_type?: "markdown";
|
||||||
|
children?: NamedMDCellSlots;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateProps {
|
||||||
|
isCellFocused: boolean;
|
||||||
|
isEditorFocused: boolean;
|
||||||
|
cell?: ImmutableCell;
|
||||||
|
markdownOptions: ReactMarkdownProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DispatchProps {
|
||||||
|
focusAboveCell: () => void;
|
||||||
|
focusBelowCell: () => void;
|
||||||
|
focusEditor: () => void;
|
||||||
|
unfocusEditor: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add missing style to make the editor show https://github.com/nteract/nteract/commit/7fa580011578350e56deac81359f6294fdfcad20#diff-07829a1908e4bf98d4420f868a1c6f890b95d77297b9805c9590d2dba11e80ce
|
||||||
|
export const Source = styled(BareSource)`
|
||||||
|
width: 100%;
|
||||||
|
width: -webkit-fill-available;
|
||||||
|
width: -moz-available;
|
||||||
|
`;
|
||||||
|
export class PureMarkdownCell extends React.Component<ComponentProps & DispatchProps & StateProps> {
|
||||||
|
render() {
|
||||||
|
const { contentRef, id, cell, children } = this.props;
|
||||||
|
|
||||||
|
const { isEditorFocused, isCellFocused, markdownOptions } = this.props;
|
||||||
|
|
||||||
|
const { focusAboveCell, focusBelowCell, focusEditor, unfocusEditor } = this.props;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We don't set the editor slots as defaults to support dynamic imports
|
||||||
|
* Users can continue to add the editorSlots as children
|
||||||
|
*/
|
||||||
|
const editor = children?.editor;
|
||||||
|
const toolbar = children?.toolbar;
|
||||||
|
|
||||||
|
const source = cell ? cell.get("source", "") : "";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="nteract-md-cell nteract-cell">
|
||||||
|
<div className="nteract-cell-row">
|
||||||
|
<div className="nteract-cell-gutter">{toolbar && toolbar()}</div>
|
||||||
|
<div className="nteract-cell-body">
|
||||||
|
<MarkdownPreviewer
|
||||||
|
focusAbove={focusAboveCell}
|
||||||
|
focusBelow={focusBelowCell}
|
||||||
|
focusEditor={focusEditor}
|
||||||
|
cellFocused={isCellFocused}
|
||||||
|
editorFocused={isEditorFocused}
|
||||||
|
unfocusEditor={unfocusEditor}
|
||||||
|
source={source}
|
||||||
|
markdownOptions={markdownOptions}
|
||||||
|
>
|
||||||
|
<Source className="nteract-cell-source">
|
||||||
|
<Editor id={id} contentRef={contentRef}>
|
||||||
|
{editor}
|
||||||
|
</Editor>
|
||||||
|
</Source>
|
||||||
|
</MarkdownPreviewer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeMapStateToProps = (
|
||||||
|
initialState: AppState,
|
||||||
|
ownProps: ComponentProps
|
||||||
|
): ((state: AppState) => StateProps) => {
|
||||||
|
const { id, contentRef } = ownProps;
|
||||||
|
const mapStateToProps = (state: AppState): StateProps => {
|
||||||
|
const model = selectors.model(state, { contentRef });
|
||||||
|
let isCellFocused = false;
|
||||||
|
let isEditorFocused = false;
|
||||||
|
let cell;
|
||||||
|
|
||||||
|
if (model && model.type === "notebook") {
|
||||||
|
cell = selectors.notebook.cellById(model, { id });
|
||||||
|
isCellFocused = model.cellFocused === id;
|
||||||
|
isEditorFocused = model.editorFocused === id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const markdownOptionsDefaults = {
|
||||||
|
linkTarget: "_blank",
|
||||||
|
};
|
||||||
|
const currentMarkdownOptions = markdownConfig(state);
|
||||||
|
|
||||||
|
const markdownOptions = Object.assign({}, markdownOptionsDefaults, currentMarkdownOptions);
|
||||||
|
|
||||||
|
return {
|
||||||
|
cell,
|
||||||
|
isCellFocused,
|
||||||
|
isEditorFocused,
|
||||||
|
markdownOptions,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return mapStateToProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeMapDispatchToProps = (
|
||||||
|
initialDispatch: Dispatch,
|
||||||
|
ownProps: ComponentProps
|
||||||
|
): ((dispatch: Dispatch) => DispatchProps) => {
|
||||||
|
const { id, contentRef } = ownProps;
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
|
||||||
|
focusAboveCell: () => {
|
||||||
|
dispatch(actions.focusPreviousCell({ id, contentRef }));
|
||||||
|
dispatch(actions.focusPreviousCellEditor({ id, contentRef }));
|
||||||
|
},
|
||||||
|
focusBelowCell: () => {
|
||||||
|
dispatch(actions.focusNextCell({ id, createCellIfUndefined: true, contentRef }));
|
||||||
|
dispatch(actions.focusNextCellEditor({ id, contentRef }));
|
||||||
|
},
|
||||||
|
focusEditor: () => dispatch(actions.focusCellEditor({ id, contentRef })),
|
||||||
|
unfocusEditor: () => dispatch(actions.focusCellEditor({ id: undefined, contentRef })),
|
||||||
|
});
|
||||||
|
|
||||||
|
return mapDispatchToProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MarkdownCell = connect(makeMapStateToProps, makeMapDispatchToProps)(PureMarkdownCell);
|
||||||
|
|
||||||
|
export default MarkdownCell;
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
import { JSONObject } from "@nteract/commutable";
|
||||||
|
import { outputToJS } from "@nteract/commutable/lib/v4";
|
||||||
|
import { actions, AppState, ContentRef, selectors } from "@nteract/core";
|
||||||
|
import IframeResizer from "iframe-resizer-react";
|
||||||
|
import Immutable from "immutable";
|
||||||
|
import postRobot from "post-robot";
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Dispatch } from "redux";
|
||||||
|
import { CellOutputViewerProps } from "../../../../CellOutputViewer/CellOutputViewer";
|
||||||
|
|
||||||
|
// Adapted from https://github.com/nteract/nteract/blob/main/packages/stateful-components/src/outputs/index.tsx
|
||||||
|
// to add support for sandboxing using <iframe>
|
||||||
|
|
||||||
|
interface ComponentProps {
|
||||||
|
id: string;
|
||||||
|
contentRef: ContentRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateProps {
|
||||||
|
hidden: boolean;
|
||||||
|
expanded: boolean;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
outputs: Immutable.List<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DispatchProps {
|
||||||
|
onMetadataChange?: (metadata: JSONObject, mediaType: string, index?: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SandboxOutputs extends React.PureComponent<ComponentProps & StateProps & DispatchProps> {
|
||||||
|
private childWindow: Window;
|
||||||
|
|
||||||
|
render(): JSX.Element {
|
||||||
|
// Using min-width to set the width of the iFrame, works around an issue in iOS that can prevent the iFrame from sizing correctly.
|
||||||
|
return (
|
||||||
|
<IframeResizer
|
||||||
|
checkOrigin={false}
|
||||||
|
loading="lazy"
|
||||||
|
heightCalculationMethod="taggedElement"
|
||||||
|
onLoad={(event) => this.handleFrameLoad(event)}
|
||||||
|
src="./cellOutputViewer.html"
|
||||||
|
style={{ height: "1px", width: "1px", minWidth: "100%", border: "none" }}
|
||||||
|
sandbox="allow-downloads allow-popups allow-forms allow-pointer-lock allow-scripts allow-popups-to-escape-sandbox"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFrameLoad(event: React.SyntheticEvent<HTMLIFrameElement, Event>): void {
|
||||||
|
this.childWindow = (event.target as HTMLIFrameElement).contentWindow;
|
||||||
|
this.sendPropsToFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
sendPropsToFrame(): void {
|
||||||
|
if (!this.childWindow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props: CellOutputViewerProps = {
|
||||||
|
id: this.props.id,
|
||||||
|
contentRef: this.props.contentRef,
|
||||||
|
hidden: this.props.hidden,
|
||||||
|
expanded: this.props.expanded,
|
||||||
|
outputs: this.props.outputs.toArray().map((output) => outputToJS(output)),
|
||||||
|
onMetadataChange: this.props.onMetadataChange,
|
||||||
|
};
|
||||||
|
|
||||||
|
postRobot.send(this.childWindow, "props", props);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.sendPropsToFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(): void {
|
||||||
|
this.sendPropsToFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeMapStateToProps = (
|
||||||
|
initialState: AppState,
|
||||||
|
ownProps: ComponentProps
|
||||||
|
): ((state: AppState) => StateProps) => {
|
||||||
|
const mapStateToProps = (state: AppState): StateProps => {
|
||||||
|
let outputs = Immutable.List();
|
||||||
|
let hidden = false;
|
||||||
|
let expanded = false;
|
||||||
|
|
||||||
|
const { contentRef, id } = ownProps;
|
||||||
|
const model = selectors.model(state, { contentRef });
|
||||||
|
|
||||||
|
if (model && model.type === "notebook") {
|
||||||
|
const cell = selectors.notebook.cellById(model, { id });
|
||||||
|
if (cell) {
|
||||||
|
outputs = cell.get("outputs", Immutable.List());
|
||||||
|
hidden = cell.cell_type === "code" && cell.getIn(["metadata", "jupyter", "outputs_hidden"]);
|
||||||
|
expanded = cell.cell_type === "code" && cell.getIn(["metadata", "collapsed"]) === false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { outputs, hidden, expanded };
|
||||||
|
};
|
||||||
|
return mapStateToProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const makeMapDispatchToProps = (
|
||||||
|
initialDispath: Dispatch,
|
||||||
|
ownProps: ComponentProps
|
||||||
|
): ((dispatch: Dispatch) => DispatchProps) => {
|
||||||
|
const { id, contentRef } = ownProps;
|
||||||
|
const mapDispatchToProps = (dispatch: Dispatch) => {
|
||||||
|
return {
|
||||||
|
onMetadataChange: (metadata: JSONObject, mediaType: string, index?: number) => {
|
||||||
|
dispatch(
|
||||||
|
actions.updateOutputMetadata({
|
||||||
|
id,
|
||||||
|
contentRef,
|
||||||
|
metadata,
|
||||||
|
index: index || 0,
|
||||||
|
mediaType,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return mapDispatchToProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect<StateProps, DispatchProps, ComponentProps, AppState>(
|
||||||
|
makeMapStateToProps,
|
||||||
|
makeMapDispatchToProps
|
||||||
|
)(SandboxOutputs);
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
.shemaAnalyzerComponent {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.schemaAnalyzerCard {
|
||||||
|
max-width: 4096px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
import { ImmutableOutput } from "@nteract/commutable";
|
||||||
|
import { actions, AppState, ContentRef, KernelRef, selectors } from "@nteract/core";
|
||||||
|
import { KernelOutputError, Output, StreamText } from "@nteract/outputs";
|
||||||
|
import TransformMedia from "@nteract/stateful-components/lib/outputs/transform-media";
|
||||||
|
import { Card } from "@uifabric/react-cards";
|
||||||
|
import Immutable from "immutable";
|
||||||
|
import { FontIcon, PrimaryButton, Spinner, SpinnerSize, Stack, Text, TextField } from "office-ui-fabric-react";
|
||||||
|
import * as React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Dispatch } from "redux";
|
||||||
|
import loadTransform from "../NotebookComponent/loadTransform";
|
||||||
|
import "./SchemaAnalyzerComponent.less";
|
||||||
|
|
||||||
|
interface SchemaAnalyzerComponentPureProps {
|
||||||
|
contentRef: ContentRef;
|
||||||
|
kernelRef: KernelRef;
|
||||||
|
databaseId: string;
|
||||||
|
collectionId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SchemaAnalyzerComponentDispatchProps {
|
||||||
|
runCell: (contentRef: ContentRef, cellId: string) => void;
|
||||||
|
addTransform: (transform: React.ComponentType & { MIMETYPE: string }) => void;
|
||||||
|
updateCell: (text: string, id: string, contentRef: ContentRef) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutputType = "rich" | "json";
|
||||||
|
|
||||||
|
interface SchemaAnalyzerComponentState {
|
||||||
|
outputType: OutputType;
|
||||||
|
filter?: string;
|
||||||
|
isFiltering: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SchemaAnalyzerComponentProps = SchemaAnalyzerComponentPureProps &
|
||||||
|
StateProps &
|
||||||
|
SchemaAnalyzerComponentDispatchProps;
|
||||||
|
|
||||||
|
export class SchemaAnalyzerComponent extends React.Component<
|
||||||
|
SchemaAnalyzerComponentProps,
|
||||||
|
SchemaAnalyzerComponentState
|
||||||
|
> {
|
||||||
|
constructor(props: SchemaAnalyzerComponentProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
outputType: "rich",
|
||||||
|
isFiltering: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
loadTransform(this.props);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onFilterTextFieldChange = (
|
||||||
|
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
|
||||||
|
newValue?: string
|
||||||
|
): void => {
|
||||||
|
this.setState({
|
||||||
|
filter: newValue,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onAnalyzeButtonClick = () => {
|
||||||
|
const query = {
|
||||||
|
command: "listSchema",
|
||||||
|
database: this.props.databaseId,
|
||||||
|
collection: this.props.collectionId,
|
||||||
|
outputType: this.state.outputType,
|
||||||
|
filter: this.state.filter,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.state.filter) {
|
||||||
|
this.setState({
|
||||||
|
isFiltering: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.updateCell(JSON.stringify(query), this.props.firstCellId, this.props.contentRef);
|
||||||
|
this.props.runCell(this.props.contentRef, this.props.firstCellId);
|
||||||
|
};
|
||||||
|
|
||||||
|
render(): JSX.Element {
|
||||||
|
const { firstCellId: id, contentRef, kernelStatus, outputs } = this.props;
|
||||||
|
if (!id) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isKernelBusy = kernelStatus === "busy";
|
||||||
|
const isKernelIdle = kernelStatus === "idle";
|
||||||
|
const showSchemaOutput = isKernelIdle && outputs.size > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack className="schemaAnalyzerComponent" horizontalAlign="center" tokens={{ childrenGap: 20, padding: 20 }}>
|
||||||
|
<Stack.Item grow styles={{ root: { display: "contents" } }}>
|
||||||
|
<Stack horizontal tokens={{ childrenGap: 20 }} styles={{ root: { width: "100%" } }}>
|
||||||
|
<Stack.Item grow align="end">
|
||||||
|
<TextField
|
||||||
|
value={this.state.filter}
|
||||||
|
onChange={this.onFilterTextFieldChange}
|
||||||
|
label="Filter"
|
||||||
|
placeholder="{ field: 'value' }"
|
||||||
|
disabled={!isKernelIdle}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item align="end">
|
||||||
|
<PrimaryButton
|
||||||
|
text={isKernelBusy ? "Analyzing..." : "Analyze"}
|
||||||
|
onClick={this.onAnalyzeButtonClick}
|
||||||
|
disabled={!isKernelIdle}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
</Stack>
|
||||||
|
</Stack.Item>
|
||||||
|
|
||||||
|
{showSchemaOutput ? (
|
||||||
|
outputs.map((output, index) => (
|
||||||
|
<Card className="schemaAnalyzerCard" key={index}>
|
||||||
|
<Card.Item tokens={{ padding: 10 }}>
|
||||||
|
<Output output={output}>
|
||||||
|
<TransformMedia output_type={"display_data"} id={id} contentRef={contentRef} />
|
||||||
|
<TransformMedia output_type={"execute_result"} id={id} contentRef={contentRef} />
|
||||||
|
<KernelOutputError />
|
||||||
|
<StreamText />
|
||||||
|
</Output>
|
||||||
|
</Card.Item>
|
||||||
|
</Card>
|
||||||
|
))
|
||||||
|
) : this.state.isFiltering ? (
|
||||||
|
<Stack.Item>
|
||||||
|
{isKernelBusy && <Spinner styles={{ root: { marginTop: 40 } }} size={SpinnerSize.large} />}
|
||||||
|
</Stack.Item>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Stack.Item>
|
||||||
|
<FontIcon iconName="Chart" style={{ fontSize: 100, color: "#43B1E5", marginTop: 40 }} />
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item>
|
||||||
|
<Text variant="xxLarge">Explore your schema</Text>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item>
|
||||||
|
<Text variant="large">
|
||||||
|
Quickly visualize your schema to infer the frequency, types and ranges of fields in your data set.
|
||||||
|
</Text>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item>
|
||||||
|
<PrimaryButton
|
||||||
|
styles={{ root: { fontSize: 18, padding: 30 } }}
|
||||||
|
text={isKernelBusy ? "Analyzing..." : "Analyze Schema"}
|
||||||
|
onClick={this.onAnalyzeButtonClick}
|
||||||
|
disabled={kernelStatus !== "idle"}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item>{isKernelBusy && <Spinner size={SpinnerSize.large} />}</Stack.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateProps {
|
||||||
|
firstCellId: string;
|
||||||
|
kernelStatus: string;
|
||||||
|
outputs: Immutable.List<ImmutableOutput>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InitialProps {
|
||||||
|
kernelRef: string;
|
||||||
|
contentRef: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redux
|
||||||
|
const makeMapStateToProps = (state: AppState, initialProps: InitialProps) => {
|
||||||
|
const { kernelRef, contentRef } = initialProps;
|
||||||
|
const mapStateToProps = (state: AppState) => {
|
||||||
|
let kernelStatus;
|
||||||
|
let firstCellId;
|
||||||
|
let outputs;
|
||||||
|
|
||||||
|
const kernel = selectors.kernel(state, { kernelRef });
|
||||||
|
if (kernel) {
|
||||||
|
kernelStatus = kernel.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = selectors.content(state, { contentRef });
|
||||||
|
if (content?.type === "notebook") {
|
||||||
|
const cellOrder = selectors.notebook.cellOrder(content.model);
|
||||||
|
if (cellOrder.size > 0) {
|
||||||
|
firstCellId = cellOrder.first() as string;
|
||||||
|
|
||||||
|
const model = selectors.model(state, { contentRef });
|
||||||
|
if (model && model.type === "notebook") {
|
||||||
|
const cell = selectors.notebook.cellById(model, { id: firstCellId });
|
||||||
|
if (cell) {
|
||||||
|
outputs = cell.get("outputs", Immutable.List());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
firstCellId,
|
||||||
|
kernelStatus,
|
||||||
|
outputs,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return mapStateToProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeMapDispatchToProps = () => {
|
||||||
|
const mapDispatchToProps = (dispatch: Dispatch) => {
|
||||||
|
return {
|
||||||
|
addTransform: (transform: React.ComponentType & { MIMETYPE: string }) => {
|
||||||
|
return dispatch(
|
||||||
|
actions.addTransform({
|
||||||
|
mediaType: transform.MIMETYPE,
|
||||||
|
component: transform,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
runCell: (contentRef: ContentRef, cellId: string) => {
|
||||||
|
return dispatch(
|
||||||
|
actions.executeCell({
|
||||||
|
contentRef,
|
||||||
|
id: cellId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
updateCell: (text: string, id: string, contentRef: ContentRef) => {
|
||||||
|
dispatch(actions.updateCellSource({ id, contentRef, value: text }));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return mapDispatchToProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(makeMapStateToProps, makeMapDispatchToProps)(SchemaAnalyzerComponent);
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { Notebook } from "@nteract/commutable";
|
||||||
|
import { actions, createContentRef, createKernelRef, IContent, KernelRef } from "@nteract/core";
|
||||||
|
import * as React from "react";
|
||||||
|
import { Provider } from "react-redux";
|
||||||
|
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||||
|
import {
|
||||||
|
NotebookComponentBootstrapper,
|
||||||
|
NotebookComponentBootstrapperOptions,
|
||||||
|
} from "../NotebookComponent/NotebookComponentBootstrapper";
|
||||||
|
import SchemaAnalyzerComponent from "./SchemaAnalyzerComponent";
|
||||||
|
|
||||||
|
export class SchemaAnalyzerComponentAdapter extends NotebookComponentBootstrapper implements ReactAdapter {
|
||||||
|
public parameters: unknown;
|
||||||
|
private kernelRef: KernelRef;
|
||||||
|
|
||||||
|
constructor(options: NotebookComponentBootstrapperOptions, private databaseId: string, private collectionId: string) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
if (!this.contentRef) {
|
||||||
|
this.contentRef = createContentRef();
|
||||||
|
this.kernelRef = createKernelRef();
|
||||||
|
|
||||||
|
const notebook: Notebook = {
|
||||||
|
cells: [
|
||||||
|
{
|
||||||
|
cell_type: "code",
|
||||||
|
metadata: {},
|
||||||
|
execution_count: 0,
|
||||||
|
outputs: [],
|
||||||
|
source: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
metadata: {
|
||||||
|
kernelspec: {
|
||||||
|
displayName: "Mongo",
|
||||||
|
language: "mongocli",
|
||||||
|
name: "mongo",
|
||||||
|
},
|
||||||
|
language_info: {
|
||||||
|
file_extension: "ipynb",
|
||||||
|
mimetype: "application/json",
|
||||||
|
name: "mongo",
|
||||||
|
version: "1.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nbformat: 4,
|
||||||
|
nbformat_minor: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
const model: IContent<"notebook"> = {
|
||||||
|
name: "schema-analyzer-component-notebook.ipynb",
|
||||||
|
path: "schema-analyzer-component-notebook.ipynb",
|
||||||
|
type: "notebook",
|
||||||
|
writable: true,
|
||||||
|
created: "",
|
||||||
|
last_modified: "",
|
||||||
|
mimetype: "application/x-ipynb+json",
|
||||||
|
content: notebook,
|
||||||
|
format: "json",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Request fetching notebook content
|
||||||
|
this.getStore().dispatch(
|
||||||
|
actions.fetchContentFulfilled({
|
||||||
|
filepath: model.path,
|
||||||
|
model,
|
||||||
|
kernelRef: this.kernelRef,
|
||||||
|
contentRef: this.contentRef,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public renderComponent(): JSX.Element {
|
||||||
|
const props = {
|
||||||
|
contentRef: this.contentRef,
|
||||||
|
kernelRef: this.kernelRef,
|
||||||
|
databaseId: this.databaseId,
|
||||||
|
collectionId: this.collectionId,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Provider store={this.getStore()}>
|
||||||
|
<SchemaAnalyzerComponent {...props} />;
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import { handleOpenAction } from "./OpenActions";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
import { ActionContracts } from "../Contracts/ExplorerContracts";
|
import { ActionContracts } from "../Contracts/ExplorerContracts";
|
||||||
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
import Explorer from "./Explorer";
|
import Explorer from "./Explorer";
|
||||||
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
|
import { handleOpenAction } from "./OpenActions";
|
||||||
import AddCollectionPane from "./Panes/AddCollectionPane";
|
import AddCollectionPane from "./Panes/AddCollectionPane";
|
||||||
|
import CassandraAddCollectionPane from "./Panes/CassandraAddCollectionPane";
|
||||||
|
|
||||||
describe("OpenActions", () => {
|
describe("OpenActions", () => {
|
||||||
describe("handleOpenAction", () => {
|
describe("handleOpenAction", () => {
|
||||||
@@ -33,6 +33,7 @@ describe("OpenActions", () => {
|
|||||||
collection.expandCollection = jest.fn();
|
collection.expandCollection = jest.fn();
|
||||||
collection.onDocumentDBDocumentsClick = jest.fn();
|
collection.onDocumentDBDocumentsClick = jest.fn();
|
||||||
collection.onMongoDBDocumentsClick = jest.fn();
|
collection.onMongoDBDocumentsClick = jest.fn();
|
||||||
|
collection.onSchemaAnalyzerClick = jest.fn();
|
||||||
collection.onTableEntitiesClick = jest.fn();
|
collection.onTableEntitiesClick = jest.fn();
|
||||||
collection.onGraphDocumentsClick = jest.fn();
|
collection.onGraphDocumentsClick = jest.fn();
|
||||||
collection.onNewQueryClick = jest.fn();
|
collection.onNewQueryClick = jest.fn();
|
||||||
|
|||||||
@@ -79,6 +79,14 @@ function openCollectionTab(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
action.tabKind === ActionContracts.TabKind.SchemaAnalyzer ||
|
||||||
|
(<any>action).tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SchemaAnalyzer]
|
||||||
|
) {
|
||||||
|
collection.onSchemaAnalyzerClick();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
action.tabKind === ActionContracts.TabKind.TableEntities ||
|
action.tabKind === ActionContracts.TabKind.TableEntities ||
|
||||||
(<any>action).tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities]
|
(<any>action).tabKind === ActionContracts.TabKind[ActionContracts.TabKind.TableEntities]
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import AddCollectionPane from "./AddCollectionPane";
|
|
||||||
import Explorer from "../Explorer";
|
|
||||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
|
import { updateUserContext } from "../../UserContext";
|
||||||
|
import Explorer from "../Explorer";
|
||||||
|
import AddCollectionPane from "./AddCollectionPane";
|
||||||
|
|
||||||
describe("Add Collection Pane", () => {
|
describe("Add Collection Pane", () => {
|
||||||
describe("isValid()", () => {
|
describe("isValid()", () => {
|
||||||
@@ -50,7 +51,14 @@ describe("Add Collection Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should be false if graph API and partition key is /id or /label", () => {
|
it("should be false if graph API and partition key is /id or /label", () => {
|
||||||
explorer.defaultExperience(Constants.DefaultAccountExperience.Graph.toLowerCase());
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableGremlin" }],
|
||||||
|
},
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
|
|
||||||
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
|
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
|
||||||
addCollectionPane.partitionKey("/id");
|
addCollectionPane.partitionKey("/id");
|
||||||
expect(addCollectionPane.isValid()).toBe(false);
|
expect(addCollectionPane.isValid()).toBe(false);
|
||||||
@@ -60,7 +68,13 @@ describe("Add Collection Pane", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should be true for any non-graph API with /id or /label partition key", () => {
|
it("should be true for any non-graph API with /id or /label partition key", () => {
|
||||||
explorer.defaultExperience(Constants.DefaultAccountExperience.DocumentDB.toLowerCase());
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
|
properties: {
|
||||||
|
capabilities: [{ name: "EnableCassandra" }],
|
||||||
|
},
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
|
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
|
||||||
|
|
||||||
addCollectionPane.partitionKey("/id");
|
addCollectionPane.partitionKey("/id");
|
||||||
|
|||||||
@@ -127,13 +127,13 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
});
|
});
|
||||||
this.partitionKey.extend({ rateLimit: 100 });
|
this.partitionKey.extend({ rateLimit: 100 });
|
||||||
this.partitionKeyPattern = ko.pureComputed(() => {
|
this.partitionKeyPattern = ko.pureComputed(() => {
|
||||||
if (this.container && this.container.isPreferredApiGraph()) {
|
if (userContext.apiType === "Gremlin") {
|
||||||
return "^/[^/]*";
|
return "^/[^/]*";
|
||||||
}
|
}
|
||||||
return ".*";
|
return ".*";
|
||||||
});
|
});
|
||||||
this.partitionKeyTitle = ko.pureComputed(() => {
|
this.partitionKeyTitle = ko.pureComputed(() => {
|
||||||
if (this.container && this.container.isPreferredApiGraph()) {
|
if (userContext.apiType === "Gremlin") {
|
||||||
return "May not use composite partition key";
|
return "May not use composite partition key";
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
@@ -331,7 +331,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
|
|
||||||
if (currentCollections >= maxCollections) {
|
if (currentCollections >= maxCollections) {
|
||||||
let typeOfContainer = "collection";
|
let typeOfContainer = "collection";
|
||||||
if (this.container.isPreferredApiGraph() || this.container.isPreferredApiTable()) {
|
if (userContext.apiType === "Gremlin" || userContext.apiType === "Tables") {
|
||||||
typeOfContainer = "container";
|
typeOfContainer = "container";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,7 +368,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
return "e.g., address.zipCode";
|
return "e.g., address.zipCode";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.container && !!this.container.isPreferredApiGraph()) {
|
if (userContext.apiType === "Gremlin") {
|
||||||
return "e.g., /address";
|
return "e.g., /address";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,21 +384,15 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.uniqueKeysVisible = ko.pureComputed<boolean>(() => {
|
this.uniqueKeysVisible = ko.pureComputed<boolean>(() => {
|
||||||
if (
|
if (userContext.apiType === "SQL") {
|
||||||
this.container == null ||
|
return true;
|
||||||
!!this.container.isPreferredApiMongoDB() ||
|
|
||||||
!!this.container.isPreferredApiTable() ||
|
|
||||||
!!this.container.isPreferredApiCassandra() ||
|
|
||||||
!!this.container.isPreferredApiGraph()
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.partitionKeyVisible = ko.computed<boolean>(() => {
|
this.partitionKeyVisible = ko.computed<boolean>(() => {
|
||||||
if (this.container == null || !!this.container.isPreferredApiTable()) {
|
if (this.container == null || userContext.apiType === "Tables") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,7 +585,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.container.isPreferredApiDocumentDB()) {
|
if (userContext.apiType === "SQL") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -599,7 +593,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.container.isPreferredApiCassandra() && this.container.hasStorageAnalyticsAfecFeature()) {
|
if (userContext.apiType === "Cassandra" && this.container.hasStorageAnalyticsAfecFeature()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -763,7 +757,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!!this.container.isPreferredApiTable()) {
|
if (userContext.apiType === "Tables") {
|
||||||
// Table require fixed Database: TablesDB, and fixed Partition Key: /'$pk'
|
// Table require fixed Database: TablesDB, and fixed Partition Key: /'$pk'
|
||||||
this.databaseId(SharedConstants.CollectionCreation.TablesAPIDefaultDatabase);
|
this.databaseId(SharedConstants.CollectionCreation.TablesAPIDefaultDatabase);
|
||||||
this.partitionKey("/'$pk'");
|
this.partitionKey("/'$pk'");
|
||||||
@@ -923,8 +917,10 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
this.databaseId("");
|
this.databaseId("");
|
||||||
this.partitionKey("");
|
this.partitionKey("");
|
||||||
this.throughputSpendAck(false);
|
this.throughputSpendAck(false);
|
||||||
this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled());
|
if (!this.container.isServerlessEnabled()) {
|
||||||
this.isSharedAutoPilotSelected(this.container.isAutoscaleDefaultEnabled());
|
this.isAutoPilotSelected(this.container.isAutoscaleDefaultEnabled());
|
||||||
|
this.isSharedAutoPilotSelected(this.container.isAutoscaleDefaultEnabled());
|
||||||
|
}
|
||||||
this.autoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
this.autoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
this.sharedAutoPilotThroughput(AutoPilotUtils.minAutoPilotThroughput);
|
||||||
|
|
||||||
@@ -958,7 +954,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public isNonTableApi = (): boolean => {
|
public isNonTableApi = (): boolean => {
|
||||||
return !this.container.isPreferredApiTable();
|
return userContext.apiType !== "Tables";
|
||||||
};
|
};
|
||||||
|
|
||||||
public isUnlimitedStorageSelected = (): boolean => {
|
public isUnlimitedStorageSelected = (): boolean => {
|
||||||
@@ -1011,7 +1007,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.container.isPreferredApiGraph() && (this.partitionKey() === "/id" || this.partitionKey() === "/label")) {
|
if (userContext.apiType === "Gremlin" && (this.partitionKey() === "/id" || this.partitionKey() === "/label")) {
|
||||||
this.formErrors("/id and /label as partition keys are not allowed for graph.");
|
this.formErrors("/id and /label as partition keys are not allowed for graph.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1032,7 +1028,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
|
|||||||
|
|
||||||
private _setFocus() {
|
private _setFocus() {
|
||||||
// Autofocus is enabled on AddCollectionPane based on the preferred API
|
// Autofocus is enabled on AddCollectionPane based on the preferred API
|
||||||
if (this.container.isPreferredApiTable()) {
|
if (userContext.apiType === "Tables") {
|
||||||
const focusTableId = document.getElementById("containerId");
|
const focusTableId = document.getElementById("containerId");
|
||||||
focusTableId && focusTableId.focus();
|
focusTableId && focusTableId.focus();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -62,21 +62,21 @@ export default class AddDatabasePane extends ContextualPaneBase {
|
|||||||
this.databaseCreateNewShared = ko.observable<boolean>(this.getSharedThroughputDefault());
|
this.databaseCreateNewShared = ko.observable<boolean>(this.getSharedThroughputDefault());
|
||||||
|
|
||||||
this.databaseIdLabel = ko.computed<string>(() =>
|
this.databaseIdLabel = ko.computed<string>(() =>
|
||||||
this.container.isPreferredApiCassandra() ? "Keyspace id" : "Database id"
|
userContext.apiType === "Cassandra" ? "Keyspace id" : "Database id"
|
||||||
);
|
);
|
||||||
|
|
||||||
this.databaseIdPlaceHolder = ko.computed<string>(() =>
|
this.databaseIdPlaceHolder = ko.computed<string>(() =>
|
||||||
this.container.isPreferredApiCassandra() ? "Type a new keyspace id" : "Type a new database id"
|
userContext.apiType === "Cassandra" ? "Type a new keyspace id" : "Type a new database id"
|
||||||
);
|
);
|
||||||
|
|
||||||
this.databaseIdTooltipText = ko.computed<string>(() => {
|
this.databaseIdTooltipText = ko.computed<string>(() => {
|
||||||
const isCassandraAccount: boolean = this.container.isPreferredApiCassandra();
|
const isCassandraAccount: boolean = userContext.apiType === "Cassandra";
|
||||||
return `A ${isCassandraAccount ? "keyspace" : "database"} is a logical container of one or more ${
|
return `A ${isCassandraAccount ? "keyspace" : "database"} is a logical container of one or more ${
|
||||||
isCassandraAccount ? "tables" : "collections"
|
isCassandraAccount ? "tables" : "collections"
|
||||||
}`;
|
}`;
|
||||||
});
|
});
|
||||||
this.databaseLevelThroughputTooltipText = ko.computed<string>(() => {
|
this.databaseLevelThroughputTooltipText = ko.computed<string>(() => {
|
||||||
const isCassandraAccount: boolean = this.container.isPreferredApiCassandra();
|
const isCassandraAccount: boolean = userContext.apiType === "Cassandra";
|
||||||
const databaseLabel: string = isCassandraAccount ? "keyspace" : "database";
|
const databaseLabel: string = isCassandraAccount ? "keyspace" : "database";
|
||||||
const collectionsLabel: string = isCassandraAccount ? "tables" : "collections";
|
const collectionsLabel: string = isCassandraAccount ? "tables" : "collections";
|
||||||
return `Provisioned throughput at the ${databaseLabel} level will be shared across all ${collectionsLabel} within the ${databaseLabel}.`;
|
return `Provisioned throughput at the ${databaseLabel} level will be shared across all ${collectionsLabel} within the ${databaseLabel}.`;
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import React from "react";
|
|||||||
import { QueriesClient } from "../../../Common/QueriesClient";
|
import { QueriesClient } from "../../../Common/QueriesClient";
|
||||||
import { Query } from "../../../Contracts/DataModels";
|
import { Query } from "../../../Contracts/DataModels";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { BrowseQueriesPanel } from "./index";
|
import { BrowseQueriesPane } from "./BrowseQueriesPane";
|
||||||
|
|
||||||
describe("Browse queries panel", () => {
|
describe("Browse queries panel", () => {
|
||||||
const fakeExplorer = {} as Explorer;
|
const fakeExplorer = {} as Explorer;
|
||||||
fakeExplorer.canSaveQueries = ko.computed<boolean>(() => true);
|
fakeExplorer.canSaveQueries = ko.computed<boolean>(() => true);
|
||||||
const fakeClientQuery = {} as QueriesClient;
|
const fakeClientQuery = {} as QueriesClient;
|
||||||
const fakeQueryData = {} as Query[];
|
const fakeQueryData = [] as Query[];
|
||||||
fakeClientQuery.getQueries = async () => fakeQueryData;
|
fakeClientQuery.getQueries = async () => fakeQueryData;
|
||||||
fakeExplorer.queriesClient = fakeClientQuery;
|
fakeExplorer.queriesClient = fakeClientQuery;
|
||||||
const props = {
|
const props = {
|
||||||
@@ -19,12 +19,12 @@ describe("Browse queries panel", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
it("Should render Default properly", () => {
|
it("Should render Default properly", () => {
|
||||||
const wrapper = mount(<BrowseQueriesPanel {...props} />);
|
const wrapper = mount(<BrowseQueriesPane {...props} />);
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should show empty view when query is empty []", () => {
|
it("Should show empty view when query is empty []", () => {
|
||||||
const wrapper = mount(<BrowseQueriesPanel {...props} />);
|
const wrapper = mount(<BrowseQueriesPane {...props} />);
|
||||||
expect(wrapper.exists("#emptyQueryBanner")).toBe(true);
|
expect(wrapper.exists("#emptyQueryBanner")).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -13,15 +13,15 @@ import {
|
|||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import QueryTab from "../../Tabs/QueryTab";
|
import QueryTab from "../../Tabs/QueryTab";
|
||||||
|
|
||||||
interface BrowseQueriesPanelProps {
|
interface BrowseQueriesPaneProps {
|
||||||
explorer: Explorer;
|
explorer: Explorer;
|
||||||
closePanel: () => void;
|
closePanel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BrowseQueriesPanel: FunctionComponent<BrowseQueriesPanelProps> = ({
|
export const BrowseQueriesPane: FunctionComponent<BrowseQueriesPaneProps> = ({
|
||||||
explorer,
|
explorer,
|
||||||
closePanel,
|
closePanel,
|
||||||
}: BrowseQueriesPanelProps): JSX.Element => {
|
}: BrowseQueriesPaneProps): JSX.Element => {
|
||||||
const loadSavedQuery = (savedQuery: Query): void => {
|
const loadSavedQuery = (savedQuery: Query): void => {
|
||||||
const selectedCollection: Collection = explorer && explorer.findSelectedCollection();
|
const selectedCollection: Collection = explorer && explorer.findSelectedCollection();
|
||||||
if (!selectedCollection) {
|
if (!selectedCollection) {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Browse queries panel Should render Default properly 1`] = `
|
exports[`Browse queries panel Should render Default properly 1`] = `
|
||||||
<BrowseQueriesPanel
|
<BrowseQueriesPane
|
||||||
closePanel={[Function]}
|
closePanel={[Function]}
|
||||||
explorer={
|
explorer={
|
||||||
Object {
|
Object {
|
||||||
@@ -54,5 +54,5 @@ exports[`Browse queries panel Should render Default properly 1`] = `
|
|||||||
</QueriesGridComponent>
|
</QueriesGridComponent>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</BrowseQueriesPanel>
|
</BrowseQueriesPane>
|
||||||
`;
|
`;
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
import ko from "knockout";
|
|
||||||
import * as React from "react";
|
|
||||||
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
|
||||||
import { JunoClient, IPinnedRepo } from "../../Juno/JunoClient";
|
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import Explorer from "../Explorer";
|
|
||||||
import { GenericRightPaneComponent, GenericRightPaneProps } from "./GenericRightPaneComponent";
|
|
||||||
import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent";
|
|
||||||
import { IDropdownOption } from "office-ui-fabric-react";
|
|
||||||
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
|
|
||||||
import { HttpStatusCodes } from "../../Common/Constants";
|
|
||||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
|
||||||
import { NotebookContentItemType, NotebookContentItem } from "../Notebook/NotebookContentItem";
|
|
||||||
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
|
||||||
import { handleError, getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
|
||||||
|
|
||||||
interface Location {
|
|
||||||
type: "MyNotebooks" | "GitHub";
|
|
||||||
|
|
||||||
// GitHub
|
|
||||||
owner?: string;
|
|
||||||
repo?: string;
|
|
||||||
branch?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CopyNotebookPaneAdapter implements ReactAdapter {
|
|
||||||
private static readonly BranchNameWhiteSpace = " ";
|
|
||||||
|
|
||||||
parameters: ko.Observable<number>;
|
|
||||||
private isOpened: boolean;
|
|
||||||
private isExecuting: boolean;
|
|
||||||
private formError: string;
|
|
||||||
private formErrorDetail: string;
|
|
||||||
private name: string;
|
|
||||||
private content: string;
|
|
||||||
private pinnedRepos: IPinnedRepo[];
|
|
||||||
private selectedLocation: Location;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private container: Explorer,
|
|
||||||
private junoClient: JunoClient,
|
|
||||||
private gitHubOAuthService: GitHubOAuthService
|
|
||||||
) {
|
|
||||||
this.parameters = ko.observable(Date.now());
|
|
||||||
this.reset();
|
|
||||||
this.triggerRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderComponent(): JSX.Element {
|
|
||||||
if (!this.isOpened) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const genericPaneProps: GenericRightPaneProps = {
|
|
||||||
container: this.container,
|
|
||||||
formError: this.formError,
|
|
||||||
formErrorDetail: this.formErrorDetail,
|
|
||||||
id: "copynotebookpane",
|
|
||||||
isExecuting: this.isExecuting,
|
|
||||||
title: "Copy notebook",
|
|
||||||
submitButtonText: "OK",
|
|
||||||
onClose: () => this.close(),
|
|
||||||
onSubmit: () => this.submit(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const copyNotebookPaneProps: CopyNotebookPaneProps = {
|
|
||||||
name: this.name,
|
|
||||||
pinnedRepos: this.pinnedRepos,
|
|
||||||
onDropDownChange: this.onDropDownChange,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<GenericRightPaneComponent {...genericPaneProps}>
|
|
||||||
<CopyNotebookPaneComponent {...copyNotebookPaneProps} />
|
|
||||||
</GenericRightPaneComponent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public triggerRender(): void {
|
|
||||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async open(name: string, content: string): Promise<void> {
|
|
||||||
this.name = name;
|
|
||||||
this.content = content;
|
|
||||||
|
|
||||||
this.isOpened = true;
|
|
||||||
this.triggerRender();
|
|
||||||
|
|
||||||
if (this.gitHubOAuthService.isLoggedIn()) {
|
|
||||||
const response = await this.junoClient.getPinnedRepos(this.gitHubOAuthService.getTokenObservable()()?.scope);
|
|
||||||
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
|
|
||||||
handleError(`Received HTTP ${response.status} when fetching pinned repos`, "CopyNotebookPaneAdapter/submit");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.data?.length > 0) {
|
|
||||||
this.pinnedRepos = response.data;
|
|
||||||
this.triggerRender();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public close(): void {
|
|
||||||
this.reset();
|
|
||||||
this.triggerRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async submit(): Promise<void> {
|
|
||||||
let destination: string = this.selectedLocation?.type;
|
|
||||||
let clearMessage: () => void;
|
|
||||||
this.isExecuting = true;
|
|
||||||
this.triggerRender();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!this.selectedLocation) {
|
|
||||||
throw new Error(`No location selected`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.selectedLocation.type === "GitHub") {
|
|
||||||
destination = `${destination} - ${GitHubUtils.toRepoFullName(
|
|
||||||
this.selectedLocation.owner,
|
|
||||||
this.selectedLocation.repo
|
|
||||||
)} - ${this.selectedLocation.branch}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${this.name} to ${destination}`);
|
|
||||||
|
|
||||||
const notebookContentItem = await this.copyNotebook(this.selectedLocation);
|
|
||||||
if (!notebookContentItem) {
|
|
||||||
throw new Error(`Failed to upload ${this.name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationConsoleUtils.logConsoleInfo(`Successfully copied ${this.name} to ${destination}`);
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = getErrorMessage(error);
|
|
||||||
this.formError = `Failed to copy ${this.name} to ${destination}`;
|
|
||||||
this.formErrorDetail = `${errorMessage}`;
|
|
||||||
handleError(errorMessage, "CopyNotebookPaneAdapter/submit", this.formError);
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
clearMessage && clearMessage();
|
|
||||||
this.isExecuting = false;
|
|
||||||
this.triggerRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private copyNotebook = async (location: Location): Promise<NotebookContentItem> => {
|
|
||||||
let parent: NotebookContentItem;
|
|
||||||
switch (location.type) {
|
|
||||||
case "MyNotebooks":
|
|
||||||
parent = {
|
|
||||||
name: ResourceTreeAdapter.MyNotebooksTitle,
|
|
||||||
path: this.container.getNotebookBasePath(),
|
|
||||||
type: NotebookContentItemType.Directory,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "GitHub":
|
|
||||||
parent = {
|
|
||||||
name: ResourceTreeAdapter.GitHubReposTitle,
|
|
||||||
path: GitHubUtils.toContentUri(
|
|
||||||
this.selectedLocation.owner,
|
|
||||||
this.selectedLocation.repo,
|
|
||||||
this.selectedLocation.branch,
|
|
||||||
""
|
|
||||||
),
|
|
||||||
type: NotebookContentItemType.Directory,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported location type ${location.type}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.container.uploadFile(this.name, this.content, parent);
|
|
||||||
};
|
|
||||||
|
|
||||||
private onDropDownChange = (_: React.FormEvent<HTMLDivElement>, option?: IDropdownOption): void => {
|
|
||||||
this.selectedLocation = option?.data;
|
|
||||||
};
|
|
||||||
|
|
||||||
private reset = (): void => {
|
|
||||||
this.isOpened = false;
|
|
||||||
this.isExecuting = false;
|
|
||||||
this.formError = undefined;
|
|
||||||
this.formErrorDetail = undefined;
|
|
||||||
this.name = undefined;
|
|
||||||
this.content = undefined;
|
|
||||||
this.pinnedRepos = undefined;
|
|
||||||
this.selectedLocation = undefined;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
156
src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx
Normal file
156
src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import { IDropdownOption } from "office-ui-fabric-react";
|
||||||
|
import React, { FormEvent, FunctionComponent, useEffect, useState } from "react";
|
||||||
|
import { HttpStatusCodes } from "../../../Common/Constants";
|
||||||
|
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
|
import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService";
|
||||||
|
import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient";
|
||||||
|
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||||
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
|
import Explorer from "../../Explorer";
|
||||||
|
import { NotebookContentItem, NotebookContentItemType } from "../../Notebook/NotebookContentItem";
|
||||||
|
import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter";
|
||||||
|
import {
|
||||||
|
GenericRightPaneComponent,
|
||||||
|
GenericRightPaneProps,
|
||||||
|
} from "../GenericRightPaneComponent/GenericRightPaneComponent";
|
||||||
|
import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent";
|
||||||
|
|
||||||
|
interface Location {
|
||||||
|
type: "MyNotebooks" | "GitHub";
|
||||||
|
|
||||||
|
// GitHub
|
||||||
|
owner?: string;
|
||||||
|
repo?: string;
|
||||||
|
branch?: string;
|
||||||
|
}
|
||||||
|
export interface CopyNotebookPanelProps {
|
||||||
|
name: string;
|
||||||
|
content: string;
|
||||||
|
container: Explorer;
|
||||||
|
junoClient: JunoClient;
|
||||||
|
gitHubOAuthService: GitHubOAuthService;
|
||||||
|
closePanel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CopyNotebookPane: FunctionComponent<CopyNotebookPanelProps> = ({
|
||||||
|
name,
|
||||||
|
content,
|
||||||
|
container,
|
||||||
|
junoClient,
|
||||||
|
gitHubOAuthService,
|
||||||
|
closePanel,
|
||||||
|
}: CopyNotebookPanelProps) => {
|
||||||
|
const [isExecuting, setIsExecuting] = useState<boolean>();
|
||||||
|
const [formError, setFormError] = useState<string>("");
|
||||||
|
const [formErrorDetail, setFormErrorDetail] = useState<string>("");
|
||||||
|
const [pinnedRepos, setPinnedRepos] = useState<IPinnedRepo[]>();
|
||||||
|
const [selectedLocation, setSelectedLocation] = useState<Location>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
open();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const open = async (): Promise<void> => {
|
||||||
|
if (gitHubOAuthService.isLoggedIn()) {
|
||||||
|
const response = await junoClient.getPinnedRepos(gitHubOAuthService.getTokenObservable()()?.scope);
|
||||||
|
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
|
||||||
|
handleError(`Received HTTP ${response.status} when fetching pinned repos`, "CopyNotebookPaneAdapter/submit");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.data?.length > 0) {
|
||||||
|
setPinnedRepos(response.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = async (): Promise<void> => {
|
||||||
|
let destination: string = selectedLocation?.type;
|
||||||
|
let clearMessage: () => void;
|
||||||
|
setIsExecuting(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!selectedLocation) {
|
||||||
|
throw new Error(`No location selected`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedLocation.type === "GitHub") {
|
||||||
|
destination = `${destination} - ${GitHubUtils.toRepoFullName(
|
||||||
|
selectedLocation.owner,
|
||||||
|
selectedLocation.repo
|
||||||
|
)} - ${selectedLocation.branch}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${name} to ${destination}`);
|
||||||
|
|
||||||
|
const notebookContentItem = await copyNotebook(selectedLocation);
|
||||||
|
if (!notebookContentItem) {
|
||||||
|
throw new Error(`Failed to upload ${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationConsoleUtils.logConsoleInfo(`Successfully copied ${name} to ${destination}`);
|
||||||
|
closePanel();
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
setFormError(`Failed to copy ${name} to ${destination}`);
|
||||||
|
setFormErrorDetail(`${errorMessage}`);
|
||||||
|
handleError(errorMessage, "CopyNotebookPaneAdapter/submit", formError);
|
||||||
|
} finally {
|
||||||
|
clearMessage && clearMessage();
|
||||||
|
setIsExecuting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyNotebook = async (location: Location): Promise<NotebookContentItem> => {
|
||||||
|
let parent: NotebookContentItem;
|
||||||
|
switch (location.type) {
|
||||||
|
case "MyNotebooks":
|
||||||
|
parent = {
|
||||||
|
name: ResourceTreeAdapter.MyNotebooksTitle,
|
||||||
|
path: container.getNotebookBasePath(),
|
||||||
|
type: NotebookContentItemType.Directory,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "GitHub":
|
||||||
|
parent = {
|
||||||
|
name: ResourceTreeAdapter.GitHubReposTitle,
|
||||||
|
path: GitHubUtils.toContentUri(selectedLocation.owner, selectedLocation.repo, selectedLocation.branch, ""),
|
||||||
|
type: NotebookContentItemType.Directory,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported location type ${location.type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return container.uploadFile(name, content, parent);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDropDownChange = (_: FormEvent<HTMLDivElement>, option?: IDropdownOption): void => {
|
||||||
|
setSelectedLocation(option?.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const genericPaneProps: GenericRightPaneProps = {
|
||||||
|
container,
|
||||||
|
formError,
|
||||||
|
formErrorDetail,
|
||||||
|
id: "copynotebookpane",
|
||||||
|
isExecuting: isExecuting,
|
||||||
|
title: "Copy notebook",
|
||||||
|
submitButtonText: "OK",
|
||||||
|
onClose: closePanel,
|
||||||
|
onSubmit: () => submit(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyNotebookPaneProps: CopyNotebookPaneProps = {
|
||||||
|
name,
|
||||||
|
pinnedRepos,
|
||||||
|
onDropDownChange: onDropDownChange,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GenericRightPaneComponent {...genericPaneProps}>
|
||||||
|
<CopyNotebookPaneComponent {...copyNotebookPaneProps} />
|
||||||
|
</GenericRightPaneComponent>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
import * as GitHubUtils from "../../Utils/GitHubUtils";
|
|
||||||
import * as React from "react";
|
|
||||||
import { IPinnedRepo } from "../../Juno/JunoClient";
|
|
||||||
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
|
|
||||||
import {
|
import {
|
||||||
Stack,
|
|
||||||
Label,
|
|
||||||
Text,
|
|
||||||
Dropdown,
|
Dropdown,
|
||||||
IDropdownProps,
|
|
||||||
IDropdownOption,
|
IDropdownOption,
|
||||||
SelectableOptionMenuItemType,
|
IDropdownProps,
|
||||||
IRenderFunction,
|
IRenderFunction,
|
||||||
ISelectableOption,
|
ISelectableOption,
|
||||||
|
Label,
|
||||||
|
SelectableOptionMenuItemType,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
} from "office-ui-fabric-react";
|
} from "office-ui-fabric-react";
|
||||||
|
import React, { FormEvent, FunctionComponent } from "react";
|
||||||
|
import { IPinnedRepo } from "../../../Juno/JunoClient";
|
||||||
|
import * as GitHubUtils from "../../../Utils/GitHubUtils";
|
||||||
|
import { ResourceTreeAdapter } from "../../Tree/ResourceTreeAdapter";
|
||||||
|
|
||||||
interface Location {
|
interface Location {
|
||||||
type: "MyNotebooks" | "GitHub";
|
type: "MyNotebooks" | "GitHub";
|
||||||
@@ -26,46 +26,25 @@ interface Location {
|
|||||||
export interface CopyNotebookPaneProps {
|
export interface CopyNotebookPaneProps {
|
||||||
name: string;
|
name: string;
|
||||||
pinnedRepos: IPinnedRepo[];
|
pinnedRepos: IPinnedRepo[];
|
||||||
onDropDownChange: (_: React.FormEvent<HTMLDivElement>, option?: IDropdownOption) => void;
|
onDropDownChange: (_: FormEvent<HTMLDivElement>, option?: IDropdownOption) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CopyNotebookPaneComponent extends React.Component<CopyNotebookPaneProps> {
|
export const CopyNotebookPaneComponent: FunctionComponent<CopyNotebookPaneProps> = ({
|
||||||
private static readonly BranchNameWhiteSpace = " ";
|
name,
|
||||||
|
pinnedRepos,
|
||||||
|
onDropDownChange,
|
||||||
|
}: CopyNotebookPaneProps) => {
|
||||||
|
const BranchNameWhiteSpace = " ";
|
||||||
|
|
||||||
public render(): JSX.Element {
|
const onRenderDropDownTitle: IRenderFunction<IDropdownOption[]> = (options: IDropdownOption[]): JSX.Element => {
|
||||||
const dropDownProps: IDropdownProps = {
|
|
||||||
label: "Location",
|
|
||||||
ariaLabel: "Location",
|
|
||||||
placeholder: "Select an option",
|
|
||||||
onRenderTitle: this.onRenderDropDownTitle,
|
|
||||||
onRenderOption: this.onRenderDropDownOption,
|
|
||||||
options: this.getDropDownOptions(),
|
|
||||||
onChange: this.props.onDropDownChange,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="paneMainContent">
|
|
||||||
<Stack tokens={{ childrenGap: 10 }}>
|
|
||||||
<Stack.Item>
|
|
||||||
<Label htmlFor="notebookName">Name</Label>
|
|
||||||
<Text id="notebookName">{this.props.name}</Text>
|
|
||||||
</Stack.Item>
|
|
||||||
|
|
||||||
<Dropdown {...dropDownProps} />
|
|
||||||
</Stack>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onRenderDropDownTitle: IRenderFunction<IDropdownOption[]> = (options: IDropdownOption[]): JSX.Element => {
|
|
||||||
return <span>{options.length && options[0].title}</span>;
|
return <span>{options.length && options[0].title}</span>;
|
||||||
};
|
};
|
||||||
|
|
||||||
private onRenderDropDownOption: IRenderFunction<ISelectableOption> = (option: ISelectableOption): JSX.Element => {
|
const onRenderDropDownOption: IRenderFunction<ISelectableOption> = (option: ISelectableOption): JSX.Element => {
|
||||||
return <span style={{ whiteSpace: "pre-wrap" }}>{option.text}</span>;
|
return <span style={{ whiteSpace: "pre-wrap" }}>{option.text}</span>;
|
||||||
};
|
};
|
||||||
|
|
||||||
private getDropDownOptions = (): IDropdownOption[] => {
|
const getDropDownOptions = (): IDropdownOption[] => {
|
||||||
const options: IDropdownOption[] = [];
|
const options: IDropdownOption[] = [];
|
||||||
|
|
||||||
options.push({
|
options.push({
|
||||||
@@ -77,7 +56,7 @@ export class CopyNotebookPaneComponent extends React.Component<CopyNotebookPaneP
|
|||||||
} as Location,
|
} as Location,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.props.pinnedRepos && this.props.pinnedRepos.length > 0) {
|
if (pinnedRepos && pinnedRepos.length > 0) {
|
||||||
options.push({
|
options.push({
|
||||||
key: "GitHub-Header-Divider",
|
key: "GitHub-Header-Divider",
|
||||||
text: undefined,
|
text: undefined,
|
||||||
@@ -90,7 +69,7 @@ export class CopyNotebookPaneComponent extends React.Component<CopyNotebookPaneP
|
|||||||
itemType: SelectableOptionMenuItemType.Header,
|
itemType: SelectableOptionMenuItemType.Header,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.props.pinnedRepos.forEach((pinnedRepo) => {
|
pinnedRepos.forEach((pinnedRepo) => {
|
||||||
const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name);
|
const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name);
|
||||||
options.push({
|
options.push({
|
||||||
key: `GitHub-Repo-${repoFullName}`,
|
key: `GitHub-Repo-${repoFullName}`,
|
||||||
@@ -101,7 +80,7 @@ export class CopyNotebookPaneComponent extends React.Component<CopyNotebookPaneP
|
|||||||
pinnedRepo.branches.forEach((branch) =>
|
pinnedRepo.branches.forEach((branch) =>
|
||||||
options.push({
|
options.push({
|
||||||
key: `GitHub-Repo-${repoFullName}-${branch.name}`,
|
key: `GitHub-Repo-${repoFullName}-${branch.name}`,
|
||||||
text: `${CopyNotebookPaneComponent.BranchNameWhiteSpace}${branch.name}`,
|
text: `${BranchNameWhiteSpace}${branch.name}`,
|
||||||
title: `${repoFullName} - ${branch.name}`,
|
title: `${repoFullName} - ${branch.name}`,
|
||||||
data: {
|
data: {
|
||||||
type: "GitHub",
|
type: "GitHub",
|
||||||
@@ -116,4 +95,26 @@ export class CopyNotebookPaneComponent extends React.Component<CopyNotebookPaneP
|
|||||||
|
|
||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
}
|
const dropDownProps: IDropdownProps = {
|
||||||
|
label: "Location",
|
||||||
|
ariaLabel: "Location",
|
||||||
|
placeholder: "Select an option",
|
||||||
|
onRenderTitle: onRenderDropDownTitle,
|
||||||
|
onRenderOption: onRenderDropDownOption,
|
||||||
|
options: getDropDownOptions(),
|
||||||
|
onChange: onDropDownChange,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="paneMainContent">
|
||||||
|
<Stack tokens={{ childrenGap: 10 }}>
|
||||||
|
<Stack.Item>
|
||||||
|
<Label htmlFor="notebookName">Name</Label>
|
||||||
|
<Text id="notebookName">{name}</Text>
|
||||||
|
</Stack.Item>
|
||||||
|
|
||||||
|
<Dropdown {...dropDownProps} />
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,108 +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="deletecollectionconfirmationpane">
|
|
||||||
<!-- Delete Collection Confirmation form - Start -->
|
|
||||||
<div class="contextual-pane-in">
|
|
||||||
<form
|
|
||||||
class="paneContentContainer"
|
|
||||||
data-bind="
|
|
||||||
submit: submit"
|
|
||||||
>
|
|
||||||
<!-- Delete Collection Confirmation header - Start -->
|
|
||||||
<div class="firstdivbg headerline">
|
|
||||||
<span role="heading" aria-level="2" 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>
|
|
||||||
<!-- Delete Collection Confirmation header - End -->
|
|
||||||
|
|
||||||
<div class="warningErrorContainer" data-bind="visible: !formErrors()">
|
|
||||||
<div class="warningErrorContent">
|
|
||||||
<span><img class="paneWarningIcon" src="/warning.svg" alt="Warning" /></span>
|
|
||||||
<span class="warningErrorDetailsLinkContainer">
|
|
||||||
Warning! The action you are about to take cannot be undone. Continuing will permanently delete this
|
|
||||||
resource and all of its children resources.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Delete Collection Confirmation 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="click: showErrorDetails">More details</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Delete Collection Confirmation errors - End -->
|
|
||||||
|
|
||||||
<!-- Delete Collection Confirmation inputs - Start -->
|
|
||||||
<div class="paneMainContent">
|
|
||||||
<div>
|
|
||||||
<span class="mandatoryStar">*</span> <span data-bind="text: collectionIdConfirmationText"></span>
|
|
||||||
<p>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
data-test="confirmCollectionId"
|
|
||||||
name="collectionIdConfirmation"
|
|
||||||
required
|
|
||||||
class="collid"
|
|
||||||
data-bind="value: collectionIdConfirmation, hasFocus: firstFieldHasFocus, attr: { 'aria-label': collectionIdConfirmationText }"
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div data-bind="visible: recordDeleteFeedback">
|
|
||||||
<div>Help us improve Azure Cosmos DB!</div>
|
|
||||||
<div>What is the reason why you are deleting this container?</div>
|
|
||||||
<p>
|
|
||||||
<textarea
|
|
||||||
type="text"
|
|
||||||
data-test="containerDeleteFeedback"
|
|
||||||
name="containerDeleteFeedback"
|
|
||||||
rows="3"
|
|
||||||
cols="53"
|
|
||||||
class="collid"
|
|
||||||
maxlength="512"
|
|
||||||
data-bind="value: containerDeleteFeedback"
|
|
||||||
aria-label="Help us improve Azure Cosmos DB! What is the reason why you are deleting this container?"
|
|
||||||
>
|
|
||||||
</textarea>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="paneFooter">
|
|
||||||
<div class="leftpanel-okbut">
|
|
||||||
<input type="submit" data-test="deleteCollection" value="OK" class="btncreatecoll1" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Delete Collection Confirmation inputs - End -->
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<!-- Delete Collection Confirmation form - End -->
|
|
||||||
<!-- Loader - Start -->
|
|
||||||
<div class="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" data-bind="visible: isExecuting">
|
|
||||||
<img class="dataExplorerLoader" src="/LoadingIndicator_3Squares.gif" />
|
|
||||||
</div>
|
|
||||||
<!-- Loader - End -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
import * as ko from "knockout";
|
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
|
||||||
import * as Constants from "../../Common/Constants";
|
|
||||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
|
||||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
|
||||||
import { ContextualPaneBase } from "./ContextualPaneBase";
|
|
||||||
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
|
||||||
import DeleteFeedback from "../../Common/DeleteFeedback";
|
|
||||||
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
|
|
||||||
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
|
||||||
import { deleteCollection } from "../../Common/dataAccess/deleteCollection";
|
|
||||||
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
|
||||||
|
|
||||||
export default class DeleteCollectionConfirmationPane extends ContextualPaneBase {
|
|
||||||
public collectionIdConfirmationText: ko.Observable<string>;
|
|
||||||
public collectionIdConfirmation: ko.Observable<string>;
|
|
||||||
public containerDeleteFeedback: ko.Observable<string>;
|
|
||||||
public recordDeleteFeedback: ko.Observable<boolean>;
|
|
||||||
|
|
||||||
constructor(options: ViewModels.PaneOptions) {
|
|
||||||
super(options);
|
|
||||||
this.collectionIdConfirmationText = ko.observable<string>("Confirm by typing the collection id");
|
|
||||||
this.collectionIdConfirmation = ko.observable<string>();
|
|
||||||
this.containerDeleteFeedback = ko.observable<string>();
|
|
||||||
this.recordDeleteFeedback = ko.observable<boolean>(false);
|
|
||||||
this.title("Delete Collection");
|
|
||||||
this.resetData();
|
|
||||||
}
|
|
||||||
|
|
||||||
public submit(): Promise<any> {
|
|
||||||
if (!this._isValid()) {
|
|
||||||
const selectedCollection: ViewModels.Collection = this.container.findSelectedCollection();
|
|
||||||
this.formErrors("Input collection name does not match the selected collection");
|
|
||||||
NotificationConsoleUtils.logConsoleMessage(
|
|
||||||
ConsoleDataType.Error,
|
|
||||||
`Error while deleting collection ${selectedCollection && selectedCollection.id()}: ${this.formErrors()}`
|
|
||||||
);
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.formErrors("");
|
|
||||||
this.isExecuting(true);
|
|
||||||
const selectedCollection = <ViewModels.Collection>this.container.findSelectedCollection();
|
|
||||||
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteCollection, {
|
|
||||||
collectionId: selectedCollection.id(),
|
|
||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
|
||||||
paneTitle: this.title(),
|
|
||||||
});
|
|
||||||
return deleteCollection(selectedCollection.databaseId, selectedCollection.id()).then(
|
|
||||||
() => {
|
|
||||||
this.isExecuting(false);
|
|
||||||
this.close();
|
|
||||||
this.container.selectedNode(selectedCollection.database);
|
|
||||||
this.container.tabsManager?.closeTabsByComparator(
|
|
||||||
(tab) =>
|
|
||||||
tab.node?.id() === selectedCollection.id() &&
|
|
||||||
(tab.node as ViewModels.Collection).databaseId === selectedCollection.databaseId
|
|
||||||
);
|
|
||||||
this.container.refreshAllDatabases();
|
|
||||||
this.resetData();
|
|
||||||
TelemetryProcessor.traceSuccess(
|
|
||||||
Action.DeleteCollection,
|
|
||||||
{
|
|
||||||
collectionId: selectedCollection.id(),
|
|
||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
|
||||||
paneTitle: this.title(),
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
if (this.shouldRecordFeedback()) {
|
|
||||||
let deleteFeedback = new DeleteFeedback(
|
|
||||||
this.container.databaseAccount().id,
|
|
||||||
this.container.databaseAccount().name,
|
|
||||||
DefaultExperienceUtility.getApiKindFromDefaultExperience(this.container.defaultExperience()),
|
|
||||||
this.containerDeleteFeedback()
|
|
||||||
);
|
|
||||||
|
|
||||||
TelemetryProcessor.trace(Action.DeleteCollection, ActionModifiers.Mark, {
|
|
||||||
message: JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback)),
|
|
||||||
});
|
|
||||||
|
|
||||||
this.containerDeleteFeedback("");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
this.isExecuting(false);
|
|
||||||
const errorMessage = getErrorMessage(error);
|
|
||||||
this.formErrors(errorMessage);
|
|
||||||
this.formErrorsDetails(errorMessage);
|
|
||||||
TelemetryProcessor.traceFailure(
|
|
||||||
Action.DeleteCollection,
|
|
||||||
{
|
|
||||||
collectionId: selectedCollection.id(),
|
|
||||||
dataExplorerArea: Constants.Areas.ContextualPane,
|
|
||||||
paneTitle: this.title(),
|
|
||||||
error: errorMessage,
|
|
||||||
errorStack: getErrorStack(error),
|
|
||||||
},
|
|
||||||
startKey
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public resetData() {
|
|
||||||
this.collectionIdConfirmation("");
|
|
||||||
super.resetData();
|
|
||||||
}
|
|
||||||
|
|
||||||
public open() {
|
|
||||||
this.recordDeleteFeedback(this.shouldRecordFeedback());
|
|
||||||
super.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
public shouldRecordFeedback(): boolean {
|
|
||||||
return this.container.isLastCollection() && !this.container.isSelectedDatabaseShared();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _isValid(): boolean {
|
|
||||||
const selectedCollection: ViewModels.Collection = this.container.findSelectedCollection();
|
|
||||||
|
|
||||||
if (!selectedCollection) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.collectionIdConfirmation() === selectedCollection.id();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user