mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-24 19:31:36 +00:00
Compare commits
15 Commits
fix-emulat
...
MPAC-2020-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4abfcc5e25 | ||
|
|
3d9256abc6 | ||
|
|
7f1355b1a4 | ||
|
|
3eff440680 | ||
|
|
4da0887e5e | ||
|
|
b783445130 | ||
|
|
73f2c612ed | ||
|
|
d70e30c4fc | ||
|
|
f8f1df4183 | ||
|
|
d427fc729e | ||
|
|
9c36782661 | ||
|
|
d32bd8851e | ||
|
|
23b2d8100f | ||
|
|
1662d20e8a | ||
|
|
582ac865ff |
@@ -202,7 +202,6 @@ src/Explorer/Tabs/GraphTab.ts
|
||||
src/Explorer/Tabs/MongoDocumentsTab.ts
|
||||
src/Explorer/Tabs/MongoQueryTab.ts
|
||||
src/Explorer/Tabs/MongoShellTab.ts
|
||||
src/Explorer/Tabs/NotebookTab.ts
|
||||
src/Explorer/Tabs/NotebookV2Tab.ts
|
||||
src/Explorer/Tabs/QueryTab.test.ts
|
||||
src/Explorer/Tabs/QueryTab.ts
|
||||
@@ -216,7 +215,6 @@ src/Explorer/Tabs/TabComponents.ts
|
||||
src/Explorer/Tabs/TabsBase.ts
|
||||
src/Explorer/Tabs/TriggerTab.ts
|
||||
src/Explorer/Tabs/UserDefinedFunctionTab.ts
|
||||
src/Explorer/Tabs/__mocks__/NotebookTab.ts
|
||||
src/Explorer/Tree/AccessibleVerticalList.ts
|
||||
src/Explorer/Tree/Collection.test.ts
|
||||
src/Explorer/Tree/Collection.ts
|
||||
|
||||
@@ -4,11 +4,7 @@ module.exports = {
|
||||
es6: true
|
||||
},
|
||||
plugins: ["@typescript-eslint"],
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||
globals: {
|
||||
Atomics: "readonly",
|
||||
SharedArrayBuffer: "readonly"
|
||||
@@ -40,6 +36,7 @@ module.exports = {
|
||||
}
|
||||
],
|
||||
rules: {
|
||||
curly: "error"
|
||||
curly: "error",
|
||||
"@typescript-eslint/no-unused-vars": "error"
|
||||
}
|
||||
};
|
||||
|
||||
102
.github/workflows/blank.yml
vendored
102
.github/workflows/blank.yml
vendored
@@ -8,7 +8,41 @@ on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
jobs:
|
||||
test:
|
||||
compile:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Compile TypeScript"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- run: npm ci
|
||||
- run: npm run compile
|
||||
- run: npm run compile:strict
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Check Format"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- run: npm ci
|
||||
- run: npm run format:check
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Lint"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
unittest:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Unit Tests"
|
||||
steps:
|
||||
@@ -36,13 +70,13 @@ jobs:
|
||||
path: .cache
|
||||
key: ${{ runner.os }}-build-cache
|
||||
- run: npm run pack:prod
|
||||
- run: npm run copyToConsumers
|
||||
- run: cp -r ./Contracts ./dist/contracts
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
endtoend:
|
||||
name: "End to End Tests"
|
||||
endtoendemulator:
|
||||
name: "End To End Tests | Emulator | SQL"
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@@ -67,9 +101,65 @@ jobs:
|
||||
EMULATOR_ENDPOINT: https://0.0.0.0:8081/
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
|
||||
endtoendsql:
|
||||
name: "End To End Tests | SQL"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Restore Cypress Binary Cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/Cypress
|
||||
key: ${{ runner.os }}-cypress-binary-cache
|
||||
- run: npm ci
|
||||
- name: End to End Tests
|
||||
run: |
|
||||
npm start &
|
||||
cd cypress
|
||||
npm ci
|
||||
node cleanup.js
|
||||
npm run wait-for-server
|
||||
npx cypress run --browser chrome --headless --spec "./integration/dataexplorer/SQL/*"
|
||||
shell: bash
|
||||
env:
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
|
||||
CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
|
||||
endtoendmongo:
|
||||
name: "End To End Tests | Mongo"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Restore Cypress Binary Cache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/Cypress
|
||||
key: ${{ runner.os }}-cypress-binary-cache
|
||||
- name: End to End Tests
|
||||
run: |
|
||||
npm ci
|
||||
npm start &
|
||||
cd cypress
|
||||
npm ci
|
||||
node cleanup.js
|
||||
npm run wait-for-server
|
||||
npx cypress run --browser chrome --headless --spec "./integration/dataexplorer/MONGO/*"
|
||||
shell: bash
|
||||
env:
|
||||
NODE_TLS_REJECT_UNAUTHORIZED: 0
|
||||
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
|
||||
CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
|
||||
nuget:
|
||||
name: Publish Nuget
|
||||
needs: [build, test, endtoend]
|
||||
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||
@@ -90,7 +180,7 @@ jobs:
|
||||
path: "*.nupkg"
|
||||
nugetmpac:
|
||||
name: Publish Nuget MPAC
|
||||
needs: [build, test, endtoend]
|
||||
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo]
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||
|
||||
51
CONTRIBUTING.md
Normal file
51
CONTRIBUTING.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Contribution guidelines to Data Explorer
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit https://cla.microsoft.com.
|
||||
|
||||
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
|
||||
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
|
||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
||||
## Microsoft Open Source Code of Conduct
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
|
||||
Resources:
|
||||
|
||||
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
|
||||
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
|
||||
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
|
||||
|
||||
## Browser support
|
||||
Please make sure to support all modern browsers as well as Internet Explorer 11.
|
||||
For IE support, polyfill is preferred over new usage of lodash or underscore. We already polyfill almost everything by importing babel-polyfill at the top of entry points.
|
||||
|
||||
|
||||
## Coding guidelines, conventions and recommendations
|
||||
### Typescript
|
||||
* Follow this [typescript style guide](https://github.com/excelmicro/typescript) which is based on [airbnb's style guide](https://github.com/airbnb/javascript).
|
||||
* Conventions speficic to this project:
|
||||
* Use double-quotes for string
|
||||
* Don't use null, use undefined
|
||||
* Pascal case for private static readonly fields
|
||||
* Camel case for classnames in markup
|
||||
* Don't use class unless necessary
|
||||
* Code related to notebooks should be dynamically imported so that it is loaded from a separate bundle only if the account is notebook-enabled. There are already top-level notebook components which are dynamically imported and their dependencies can be statically imported from these files.
|
||||
|
||||
### React
|
||||
* Prefer using React class components over function components and hooks unless you have a simple component and require no nested functions:
|
||||
* Nested functions may be harder to test independently
|
||||
* Switching from function component to class component later mayb be painful
|
||||
|
||||
## Testing
|
||||
Any PR should not decrease testing coverage.
|
||||
|
||||
## Recommended Tools and VS Code extensions
|
||||
* [Bookmarks](https://github.com/alefragnani/vscode-bookmarks)
|
||||
* [Bracket pair colorizer](https://github.com/CoenraadS/Bracket-Pair-Colorizer-2)
|
||||
* [GitHub Pull Requests and Issues](https://github.com/Microsoft/vscode-pull-request-github)
|
||||
12
README.md
12
README.md
@@ -82,14 +82,4 @@ Unit tests are located adjacent to the code under test and run with [Jest](https
|
||||
|
||||
# Contributing
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit https://cla.microsoft.com.
|
||||
|
||||
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
|
||||
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
|
||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
Please read the [contribution guidelines](./CONTRIBUTING.md).
|
||||
5
cypress/.gitignore
vendored
5
cypress/.gitignore
vendored
@@ -1 +1,4 @@
|
||||
cypress.env.json
|
||||
cypress.env.json
|
||||
cypress/report
|
||||
cypress/screenshots
|
||||
cypress/videos
|
||||
51
cypress/cleanup.js
Normal file
51
cypress/cleanup.js
Normal file
@@ -0,0 +1,51 @@
|
||||
// Cleans up old databases from previous test runs
|
||||
const { CosmosClient } = require("@azure/cosmos");
|
||||
|
||||
// TODO: Add support for other API connection strings
|
||||
const mongoRegex = RegExp("mongodb://.*:(.*)@(.*).mongo.cosmos.azure.com");
|
||||
|
||||
async function cleanup() {
|
||||
const connectionString = process.env.CYPRESS_CONNECTION_STRING;
|
||||
if (!connectionString) {
|
||||
throw new Error("Connection string not provided");
|
||||
}
|
||||
|
||||
let client;
|
||||
switch (true) {
|
||||
case connectionString.includes("mongodb://"): {
|
||||
const [, key, accountName] = connectionString.match(mongoRegex);
|
||||
client = new CosmosClient({
|
||||
key,
|
||||
endpoint: `https://${accountName}.documents.azure.com:443/`
|
||||
});
|
||||
break;
|
||||
}
|
||||
// TODO: Add support for other API connection strings
|
||||
default:
|
||||
client = new CosmosClient(connectionString);
|
||||
break;
|
||||
}
|
||||
|
||||
const response = await client.databases.readAll().fetchAll();
|
||||
return Promise.all(
|
||||
response.resources.map(async db => {
|
||||
const dbTimestamp = new Date(db._ts * 1000);
|
||||
const twentyMinutesAgo = new Date(Date.now() - 1000 * 60 * 20);
|
||||
if (dbTimestamp < twentyMinutesAgo) {
|
||||
await client.database(db.id).delete();
|
||||
console.log(`DELETED: ${db.id} | Timestamp: ${dbTimestamp}`);
|
||||
} else {
|
||||
console.log(`SKIPPED: ${db.id} | Timestamp: ${dbTimestamp}`);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
cleanup()
|
||||
.then(() => {
|
||||
process.exit(0);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -16,7 +16,7 @@ let crypt = require("crypto");
|
||||
|
||||
context("Mongo API Test - createDatabase", () => {
|
||||
beforeEach(() => {
|
||||
connectionString.loginUsingConnectionString(connectionString.constants.mongo);
|
||||
connectionString.loginUsingConnectionString();
|
||||
});
|
||||
|
||||
it("Create a new collection in Mongo API", () => {
|
||||
@@ -63,7 +63,7 @@ context("Mongo API Test - createDatabase", () => {
|
||||
.type(sharedKey);
|
||||
|
||||
cy.wrap($body)
|
||||
.find('input[data-test="addCollection-createCollection"]')
|
||||
.find("#submitBtnAddCollection")
|
||||
.click();
|
||||
|
||||
cy.wait(10000);
|
||||
|
||||
@@ -16,10 +16,10 @@ let crypt = require("crypto");
|
||||
|
||||
context("Mongo API Test", () => {
|
||||
beforeEach(() => {
|
||||
connectionString.loginUsingConnectionString(connectionString.constants.mongo);
|
||||
connectionString.loginUsingConnectionString();
|
||||
});
|
||||
|
||||
it("Create a new collection in Mongo API - Autopilot", () => {
|
||||
it.skip("Create a new collection in Mongo API - Autopilot", () => {
|
||||
const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`;
|
||||
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
|
||||
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
|
||||
|
||||
@@ -4,10 +4,10 @@ let crypt = require("crypto");
|
||||
|
||||
context("Mongo API Test", () => {
|
||||
beforeEach(() => {
|
||||
connectionString.loginUsingConnectionString(connectionString.constants.mongo);
|
||||
connectionString.loginUsingConnectionString();
|
||||
});
|
||||
|
||||
it("Create a new collection in existing database in Mongo API", () => {
|
||||
it.skip("Create a new collection in existing database in Mongo API", () => {
|
||||
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
|
||||
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ const connectionString = require("../../../utilities/connectionString");
|
||||
|
||||
let crypt = require("crypto");
|
||||
|
||||
context("Mongo API Test", () => {
|
||||
context.skip("Mongo API Test", () => {
|
||||
beforeEach(() => {
|
||||
connectionString.loginUsingConnectionString(connectionString.constants.mongo);
|
||||
connectionString.loginUsingConnectionString();
|
||||
});
|
||||
|
||||
it("Create a new collection in Mongo API - Provision database throughput", () => {
|
||||
|
||||
@@ -16,13 +16,14 @@ let crypt = require("crypto");
|
||||
|
||||
context("SQL API Test", () => {
|
||||
beforeEach(() => {
|
||||
connectionString.loginUsingConnectionString(connectionString.constants.sql);
|
||||
connectionString.loginUsingConnectionString();
|
||||
});
|
||||
|
||||
it("Create a new container in SQL API", () => {
|
||||
const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`;
|
||||
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
|
||||
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
|
||||
connectionString.loginUsingConnectionString();
|
||||
|
||||
cy.get("iframe").then($element => {
|
||||
const $body = $element.contents().find("body");
|
||||
@@ -63,7 +64,7 @@ context("SQL API Test", () => {
|
||||
.type(sharedKey);
|
||||
|
||||
cy.wrap($body)
|
||||
.find('input[data-test="addCollection-createCollection"]')
|
||||
.find("#submitBtnAddCollection")
|
||||
.click();
|
||||
|
||||
cy.wait(10000);
|
||||
|
||||
110
cypress/package-lock.json
generated
110
cypress/package-lock.json
generated
@@ -273,77 +273,12 @@
|
||||
"any-observable": "^0.3.0"
|
||||
}
|
||||
},
|
||||
"@types/blob-util": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/blob-util/-/blob-util-1.3.3.tgz",
|
||||
"integrity": "sha512-4ahcL/QDnpjWA2Qs16ZMQif7HjGP2cw3AGjHabybjw7Vm1EKu+cfQN1D78BaZbS1WJNa1opSMF5HNMztx7lR0w==",
|
||||
"@types/sinonjs__fake-timers": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz",
|
||||
"integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/bluebird": {
|
||||
"version": "3.5.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.29.tgz",
|
||||
"integrity": "sha512-kmVtnxTuUuhCET669irqQmPAez4KFnFVKvpleVRyfC3g+SHD1hIkFZcWLim9BVcwUBLO59o8VZE4yGCmTif8Yw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/chai": {
|
||||
"version": "4.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.7.tgz",
|
||||
"integrity": "sha512-luq8meHGYwvky0O7u0eQZdA7B4Wd9owUCqvbw2m3XCrCU8mplYOujMBbvyS547AxJkC+pGnd0Cm15eNxEUNU8g==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/chai-jquery": {
|
||||
"version": "1.1.40",
|
||||
"resolved": "https://registry.npmjs.org/@types/chai-jquery/-/chai-jquery-1.1.40.tgz",
|
||||
"integrity": "sha512-mCNEZ3GKP7T7kftKeIs7QmfZZQM7hslGSpYzKbOlR2a2HCFf9ph4nlMRA9UnuOETeOQYJVhJQK7MwGqNZVyUtQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/chai": "*",
|
||||
"@types/jquery": "*"
|
||||
}
|
||||
},
|
||||
"@types/jquery": {
|
||||
"version": "3.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.31.tgz",
|
||||
"integrity": "sha512-Lz4BAJihoFw5nRzKvg4nawXPzutkv7wmfQ5121avptaSIXlDNJCUuxZxX/G+9EVidZGuO0UBlk+YjKbwRKJigg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/sizzle": "*"
|
||||
}
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.149",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz",
|
||||
"integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/minimatch": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
|
||||
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mocha": {
|
||||
"version": "5.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz",
|
||||
"integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/sinon": {
|
||||
"version": "7.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.1.tgz",
|
||||
"integrity": "sha512-EZQUP3hSZQyTQRfiLqelC9NMWd1kqLcmQE0dMiklxBkgi84T+cHOhnKpgk4NnOWpGX863yE6+IaGnOXUNFqDnQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/sinon-chai": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.3.tgz",
|
||||
"integrity": "sha512-TOUFS6vqS0PVL1I8NGVSNcFaNJtFoyZPXZ5zur+qlhDfOmQECZZM4H4kKgca6O8L+QceX/ymODZASfUfn+y4yQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/chai": "*",
|
||||
"@types/sinon": "*"
|
||||
}
|
||||
},
|
||||
"@types/sizzle": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz",
|
||||
@@ -827,24 +762,15 @@
|
||||
}
|
||||
},
|
||||
"cypress": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-4.5.0.tgz",
|
||||
"integrity": "sha512-2A4g5FW5d2fHzq8HKUGAMVTnW6P8nlWYQALiCoGN4bqBLvgwhYM/oG9oKc2CS6LnvgHFiKivKzpm9sfk3uU3zQ==",
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-4.8.0.tgz",
|
||||
"integrity": "sha512-Lsff8lF8pq6k/ioNua783tCsxGSLp6gqGXecdIfqCkqjYiOA53XKuEf1CaQJLUVs1dHSf49eDUp/sb620oJjVQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@cypress/listr-verbose-renderer": "0.4.1",
|
||||
"@cypress/request": "2.88.5",
|
||||
"@cypress/xvfb": "1.2.4",
|
||||
"@types/blob-util": "1.3.3",
|
||||
"@types/bluebird": "3.5.29",
|
||||
"@types/chai": "4.2.7",
|
||||
"@types/chai-jquery": "1.1.40",
|
||||
"@types/jquery": "3.3.31",
|
||||
"@types/lodash": "4.14.149",
|
||||
"@types/minimatch": "3.0.3",
|
||||
"@types/mocha": "5.2.7",
|
||||
"@types/sinon": "7.5.1",
|
||||
"@types/sinon-chai": "3.2.3",
|
||||
"@types/sinonjs__fake-timers": "6.0.1",
|
||||
"@types/sizzle": "2.3.2",
|
||||
"arch": "2.1.1",
|
||||
"bluebird": "3.7.2",
|
||||
@@ -878,14 +804,6 @@
|
||||
"untildify": "4.0.0",
|
||||
"url": "0.11.0",
|
||||
"yauzl": "2.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"dashdash": {
|
||||
@@ -1081,12 +999,6 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"dev": true
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
@@ -1859,6 +1771,12 @@
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"dev": true
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "cypress run",
|
||||
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",
|
||||
"test:sql": "cypress run --browser chrome --headless --spec \"./integration/dataexplorer/SQL/*\"",
|
||||
"test:ci": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/ https-get://0.0.0.0:8081/_explorer/index.html && cypress run --browser chrome --headless",
|
||||
"test:debug": "cypress open"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cypress": "^4.5.0",
|
||||
"cypress": "^4.8.0",
|
||||
"mocha": "^7.0.1",
|
||||
"mochawesome": "^4.1.0",
|
||||
"mochawesome-merge": "^4.0.1",
|
||||
|
||||
@@ -1,56 +1,41 @@
|
||||
module.exports = {
|
||||
loginUsingConnectionString: function (api) {
|
||||
loginUsingConnectionString: function() {
|
||||
const prodUrl = Cypress.env("TEST_ENDPOINT") || "https://localhost:1234/hostedExplorer.html";
|
||||
const timeout = 15000;
|
||||
|
||||
cy.visit(prodUrl);
|
||||
cy.get('iframe[id="explorerMenu"]').should("be.visible");
|
||||
|
||||
const prodUrl = "https://cosmos.azure.com/";
|
||||
const timeout = 15000;
|
||||
|
||||
cy.visit(prodUrl);
|
||||
cy.get('iframe[id="explorerMenu"]').should("be.visible");
|
||||
|
||||
cy.get("iframe").then($element => {
|
||||
const $body = $element.contents().find("body");
|
||||
|
||||
cy.wrap($body)
|
||||
.find("#connectExplorer")
|
||||
.should("exist")
|
||||
.find("div[class='connectExplorer']")
|
||||
.should("exist")
|
||||
.find("p[class='welcomeText']")
|
||||
.should("exist");
|
||||
|
||||
cy.wrap($body.find("div > p.switchConnectTypeText"))
|
||||
.should("exist")
|
||||
.last()
|
||||
.click({ force: true });
|
||||
|
||||
const secret = Cypress.env('connectionString')[api];
|
||||
cy.get("iframe").then($element => {
|
||||
const $body = $element.contents().find("body");
|
||||
|
||||
cy.wrap($body)
|
||||
.find("input[class='inputToken']")
|
||||
.should("exist")
|
||||
.type(secret, {
|
||||
force: true
|
||||
});
|
||||
|
||||
cy.wrap($body.find("input[value='Connect']"), { timeout })
|
||||
.first()
|
||||
.click({ force: true });
|
||||
|
||||
cy.wait(15000);
|
||||
|
||||
cy.wrap($body)
|
||||
.find(".connectExplorer > p:nth-child(3)")
|
||||
.should("be.visible");
|
||||
cy.wrap($body)
|
||||
.find("#connectExplorer")
|
||||
.should("exist")
|
||||
.find("div[class='connectExplorer']")
|
||||
.should("exist")
|
||||
.find("p[class='welcomeText']")
|
||||
.should("exist");
|
||||
|
||||
});
|
||||
},
|
||||
constants:{
|
||||
sql: "sql",
|
||||
mongo: "mongo",
|
||||
table: "table",
|
||||
graph: "graph",
|
||||
cassandra: "cassandra"
|
||||
}
|
||||
cy.wrap($body.find("div > p.switchConnectTypeText"))
|
||||
.should("exist")
|
||||
.last()
|
||||
.click({ force: true });
|
||||
|
||||
}
|
||||
const secret = Cypress.env("CONNECTION_STRING");
|
||||
|
||||
cy.wrap($body)
|
||||
.find("input[class='inputToken']")
|
||||
.should("exist")
|
||||
.type(secret, {
|
||||
force: true
|
||||
});
|
||||
|
||||
cy.wrap($body.find("input[value='Connect']"), { timeout })
|
||||
.first()
|
||||
.click({ force: true });
|
||||
|
||||
cy.wait(15000);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -570,6 +570,12 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.fileImportButton {
|
||||
height: 24px;
|
||||
border: @ButtonBorderWidth solid transparent;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.fileUploadSummaryContainer {
|
||||
margin-top: 40px;
|
||||
|
||||
@@ -1016,6 +1022,18 @@ menuQuickStart {
|
||||
background: #262626;
|
||||
}
|
||||
|
||||
.panelContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.panelContentWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.contextual-pane {
|
||||
top: 0px;
|
||||
right: 0 !important;
|
||||
@@ -1232,23 +1250,25 @@ menuQuickStart {
|
||||
padding: 2px 30px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
|
||||
&:active {
|
||||
border-color: #0072c6;
|
||||
background-color: #0072c6;
|
||||
}
|
||||
}
|
||||
|
||||
.btncreatecoll1:hover {
|
||||
background: @AccentMediumHigh;
|
||||
.leftpanel-okbut .genericPaneSubmitBtn {
|
||||
border: 1px solid @AccentMediumHigh;
|
||||
background-color: @AccentMediumHigh;
|
||||
color: #fff;
|
||||
border-color: @AccentMediumHigh;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
height: 24px;
|
||||
|
||||
.btncreatecoll1:active {
|
||||
border: 1px solid #0072c6;
|
||||
background-color: #0072c6;
|
||||
color: white;
|
||||
padding: 2px 30px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
&:active {
|
||||
border-color: #0072c6;
|
||||
background-color: #0072c6;
|
||||
}
|
||||
}
|
||||
|
||||
.btncreatecoll1-off {
|
||||
@@ -1361,6 +1381,15 @@ p {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.headerline .closePaneBtn {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
width: 16px;
|
||||
height: 100%;
|
||||
margin-right: 4px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.closeImg {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
@@ -1710,6 +1739,13 @@ input::-webkit-calendar-picker-indicator {
|
||||
margin: (2 * @MediumSpace) 0px;
|
||||
}
|
||||
|
||||
.contextual-pane .panelMainContent {
|
||||
padding-left: 34px;
|
||||
padding-right: 34px;
|
||||
color: @BaseDark;
|
||||
margin: (2 * @MediumSpace) 0px;
|
||||
}
|
||||
|
||||
.contextual-pane .paneFooter {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
|
||||
599
package-lock.json
generated
599
package-lock.json
generated
@@ -5,9 +5,9 @@
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@azure/cosmos": {
|
||||
"version": "3.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.6.3.tgz",
|
||||
"integrity": "sha512-JoCDxl0TnL6EHL4xD3KC9r2bMivK13q1jl7h69wd/YFLlt3aBTTCehtAX+y4alNSENpL53XdRdw/cna0mI2XDw==",
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.7.1.tgz",
|
||||
"integrity": "sha512-P/80gz4c6gAjdilOTT7TofU3Vl1Wh21+U+/d/MAIdaXhsSspL44diyl0qfLRxXiosL4yT12SGHOE9w0n3U57yA==",
|
||||
"requires": {
|
||||
"@types/debug": "^4.1.4",
|
||||
"debug": "^4.1.1",
|
||||
@@ -17,8 +17,20 @@
|
||||
"os-name": "^3.1.0",
|
||||
"priorityqueuejs": "^1.0.0",
|
||||
"semaphore": "^1.0.5",
|
||||
"tslib": "^1.10.0",
|
||||
"uuid": "^3.3.2"
|
||||
"tslib": "^2.0.0",
|
||||
"uuid": "^8.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz",
|
||||
"integrity": "sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g=="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz",
|
||||
"integrity": "sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@azure/cosmos-language-service": {
|
||||
@@ -1047,9 +1059,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/runtime-corejs3": {
|
||||
"version": "7.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.9.6.tgz",
|
||||
"integrity": "sha512-6toWAfaALQjt3KMZQc6fABqZwUDDuWzz+cAfPhqyEnzxvdWOAkjwPNxgF8xlmo7OWLsSjaKjsskpKHRLaMArOA==",
|
||||
"version": "7.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.2.tgz",
|
||||
"integrity": "sha512-+a2M/u7r15o3dV1NEizr9bRi+KUVnrs/qYxF0Z06DAPx/4VCWaz1WA7EcbE+uqGgt39lp5akWGmHsTseIkHkHg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"core-js-pure": "^3.0.0",
|
||||
@@ -3104,12 +3116,6 @@
|
||||
"integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/events": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
|
||||
"integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/geojson": {
|
||||
"version": "7946.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.7.tgz",
|
||||
@@ -3117,12 +3123,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/glob": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
|
||||
"integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==",
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.2.tgz",
|
||||
"integrity": "sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/events": "*",
|
||||
"@types/minimatch": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
@@ -3399,33 +3404,34 @@
|
||||
"integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw=="
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "2.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.25.0.tgz",
|
||||
"integrity": "sha512-W2YyMtjmlrOjtXc+FtTelVs9OhuR6OlYc4XKIslJ8PUJOqgYYAPRJhAqkYRQo3G4sjvG8jSodsNycEn4W2gHUw==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.2.0.tgz",
|
||||
"integrity": "sha512-t9RTk/GyYilIXt6BmZurhBzuMT9kLKw3fQoJtK9ayv0tXTlznXEAnx07sCLXdkN3/tZDep1s1CEV95CWuARYWA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@typescript-eslint/experimental-utils": "2.25.0",
|
||||
"@typescript-eslint/experimental-utils": "3.2.0",
|
||||
"functional-red-black-tree": "^1.0.1",
|
||||
"regexpp": "^3.0.0",
|
||||
"semver": "^7.3.2",
|
||||
"tsutils": "^3.17.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@typescript-eslint/experimental-utils": {
|
||||
"version": "2.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.25.0.tgz",
|
||||
"integrity": "sha512-0IZ4ZR5QkFYbaJk+8eJ2kYeA+1tzOE1sBjbwwtSV85oNWYUBep+EyhlZ7DLUCyhMUGuJpcCCFL0fDtYAP1zMZw==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.2.0.tgz",
|
||||
"integrity": "sha512-UbJBsk+xO9dIFKtj16+m42EvUvsjZbbgQ2O5xSTSfVT1Z3yGkL90DVu0Hd3029FZ5/uBgl+F3Vo8FAcEcqc6aQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.3",
|
||||
"@typescript-eslint/typescript-estree": "2.25.0",
|
||||
"@typescript-eslint/typescript-estree": "3.2.0",
|
||||
"eslint-scope": "^5.0.0",
|
||||
"eslint-utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "2.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.25.0.tgz",
|
||||
"integrity": "sha512-VUksmx5lDxSi6GfmwSK7SSoIKSw9anukWWNitQPqt58LuYrKalzsgeuignbqnB+rK/xxGlSsCy8lYnwFfB6YJg==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.2.0.tgz",
|
||||
"integrity": "sha512-uh+Y2QO7dxNrdLw7mVnjUqkwO/InxEqwN0wF+Za6eo3coxls9aH9kQ/5rSvW2GcNanebRTmsT5w1/92lAOb1bA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "^4.1.1",
|
||||
@@ -3433,57 +3439,57 @@
|
||||
"glob": "^7.1.6",
|
||||
"is-glob": "^4.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"semver": "^6.3.0",
|
||||
"semver": "^7.3.2",
|
||||
"tsutils": "^3.17.1"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/experimental-utils": {
|
||||
"version": "2.33.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.33.0.tgz",
|
||||
"integrity": "sha512-qzPM2AuxtMrRq78LwyZa8Qn6gcY8obkIrBs1ehqmQADwkYzTE1Pb4y2W+U3rE/iFkSWcWHG2LS6MJfj6SmHApg==",
|
||||
"version": "2.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz",
|
||||
"integrity": "sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==",
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.3",
|
||||
"@typescript-eslint/typescript-estree": "2.33.0",
|
||||
"@typescript-eslint/typescript-estree": "2.34.0",
|
||||
"eslint-scope": "^5.0.0",
|
||||
"eslint-utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/parser": {
|
||||
"version": "2.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.25.0.tgz",
|
||||
"integrity": "sha512-mccBLaBSpNVgp191CP5W+8U1crTyXsRziWliCqzj02kpxdjKMvFHGJbK33NroquH3zB/gZ8H511HEsJBa2fNEg==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.2.0.tgz",
|
||||
"integrity": "sha512-Vhu+wwdevDLVDjK1lIcoD6ZbuOa93fzqszkaO3iCnmrScmKwyW/AGkzc2UvfE5TCoCXqq7Jyt6SOXjsIlpqF4A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/eslint-visitor-keys": "^1.0.0",
|
||||
"@typescript-eslint/experimental-utils": "2.25.0",
|
||||
"@typescript-eslint/typescript-estree": "2.25.0",
|
||||
"@typescript-eslint/experimental-utils": "3.2.0",
|
||||
"@typescript-eslint/typescript-estree": "3.2.0",
|
||||
"eslint-visitor-keys": "^1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@typescript-eslint/experimental-utils": {
|
||||
"version": "2.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.25.0.tgz",
|
||||
"integrity": "sha512-0IZ4ZR5QkFYbaJk+8eJ2kYeA+1tzOE1sBjbwwtSV85oNWYUBep+EyhlZ7DLUCyhMUGuJpcCCFL0fDtYAP1zMZw==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.2.0.tgz",
|
||||
"integrity": "sha512-UbJBsk+xO9dIFKtj16+m42EvUvsjZbbgQ2O5xSTSfVT1Z3yGkL90DVu0Hd3029FZ5/uBgl+F3Vo8FAcEcqc6aQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.3",
|
||||
"@typescript-eslint/typescript-estree": "2.25.0",
|
||||
"@typescript-eslint/typescript-estree": "3.2.0",
|
||||
"eslint-scope": "^5.0.0",
|
||||
"eslint-utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "2.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.25.0.tgz",
|
||||
"integrity": "sha512-VUksmx5lDxSi6GfmwSK7SSoIKSw9anukWWNitQPqt58LuYrKalzsgeuignbqnB+rK/xxGlSsCy8lYnwFfB6YJg==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.2.0.tgz",
|
||||
"integrity": "sha512-uh+Y2QO7dxNrdLw7mVnjUqkwO/InxEqwN0wF+Za6eo3coxls9aH9kQ/5rSvW2GcNanebRTmsT5w1/92lAOb1bA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "^4.1.1",
|
||||
@@ -3491,22 +3497,22 @@
|
||||
"glob": "^7.1.6",
|
||||
"is-glob": "^4.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"semver": "^6.3.0",
|
||||
"semver": "^7.3.2",
|
||||
"tsutils": "^3.17.1"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@typescript-eslint/typescript-estree": {
|
||||
"version": "2.33.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.33.0.tgz",
|
||||
"integrity": "sha512-d8rY6/yUxb0+mEwTShCQF2zYQdLlqihukNfG9IUlLYz5y1CH6G/9XYbrxQLq3Z14RNvkCC6oe+OcFlyUpwUbkg==",
|
||||
"version": "2.34.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz",
|
||||
"integrity": "sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==",
|
||||
"requires": {
|
||||
"debug": "^4.1.1",
|
||||
"eslint-visitor-keys": "^1.1.0",
|
||||
@@ -7526,22 +7532,22 @@
|
||||
}
|
||||
},
|
||||
"eslint": {
|
||||
"version": "6.8.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz",
|
||||
"integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==",
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.2.0.tgz",
|
||||
"integrity": "sha512-B3BtEyaDKC5MlfDa2Ha8/D6DsS4fju95zs0hjS3HdGazw+LNayai38A25qMppK37wWGWNYSPOR6oYzlz5MHsRQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"ajv": "^6.10.0",
|
||||
"chalk": "^2.1.0",
|
||||
"cross-spawn": "^6.0.5",
|
||||
"chalk": "^4.0.0",
|
||||
"cross-spawn": "^7.0.2",
|
||||
"debug": "^4.0.1",
|
||||
"doctrine": "^3.0.0",
|
||||
"eslint-scope": "^5.0.0",
|
||||
"eslint-utils": "^1.4.3",
|
||||
"eslint-visitor-keys": "^1.1.0",
|
||||
"espree": "^6.1.2",
|
||||
"esquery": "^1.0.1",
|
||||
"eslint-scope": "^5.1.0",
|
||||
"eslint-utils": "^2.0.0",
|
||||
"eslint-visitor-keys": "^1.2.0",
|
||||
"espree": "^7.1.0",
|
||||
"esquery": "^1.2.0",
|
||||
"esutils": "^2.0.2",
|
||||
"file-entry-cache": "^5.0.1",
|
||||
"functional-red-black-tree": "^1.0.1",
|
||||
@@ -7554,31 +7560,89 @@
|
||||
"is-glob": "^4.0.0",
|
||||
"js-yaml": "^3.13.1",
|
||||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||
"levn": "^0.3.0",
|
||||
"levn": "^0.4.1",
|
||||
"lodash": "^4.17.14",
|
||||
"minimatch": "^3.0.4",
|
||||
"mkdirp": "^0.5.1",
|
||||
"natural-compare": "^1.4.0",
|
||||
"optionator": "^0.8.3",
|
||||
"optionator": "^0.9.1",
|
||||
"progress": "^2.0.0",
|
||||
"regexpp": "^2.0.1",
|
||||
"semver": "^6.1.2",
|
||||
"strip-ansi": "^5.2.0",
|
||||
"strip-json-comments": "^3.0.1",
|
||||
"regexpp": "^3.1.0",
|
||||
"semver": "^7.2.1",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"strip-json-comments": "^3.1.0",
|
||||
"table": "^5.2.3",
|
||||
"text-table": "^0.2.0",
|
||||
"v8-compile-cache": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint-utils": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
|
||||
"integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
|
||||
"ansi-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
|
||||
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
|
||||
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eslint-visitor-keys": "^1.1.0"
|
||||
"@types/color-name": "^1.1.1",
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"eslint-scope": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz",
|
||||
"integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esrecurse": "^4.1.0",
|
||||
"estraverse": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"eslint-visitor-keys": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz",
|
||||
"integrity": "sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"globals": {
|
||||
"version": "12.4.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
|
||||
@@ -7588,29 +7652,116 @@
|
||||
"type-fest": "^0.8.1"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ignore": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
|
||||
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
|
||||
"dev": true
|
||||
},
|
||||
"regexpp": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
|
||||
"integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
|
||||
"levn": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"prelude-ls": "^1.2.1",
|
||||
"type-check": "~0.4.0"
|
||||
}
|
||||
},
|
||||
"optionator": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
|
||||
"integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"deep-is": "^0.1.3",
|
||||
"fast-levenshtein": "^2.0.6",
|
||||
"levn": "^0.4.1",
|
||||
"prelude-ls": "^1.2.1",
|
||||
"type-check": "^0.4.0",
|
||||
"word-wrap": "^1.2.3"
|
||||
}
|
||||
},
|
||||
"path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true
|
||||
},
|
||||
"prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
|
||||
"dev": true
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
|
||||
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz",
|
||||
"integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
|
||||
"integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"prelude-ls": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -7643,17 +7794,17 @@
|
||||
}
|
||||
},
|
||||
"eslint-plugin-jest": {
|
||||
"version": "23.8.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-23.8.2.tgz",
|
||||
"integrity": "sha512-xwbnvOsotSV27MtAe7s8uGWOori0nUsrXh2f1EnpmXua8sDfY6VZhHAhHg2sqK7HBNycRQExF074XSZ7DvfoFg==",
|
||||
"version": "23.13.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-23.13.2.tgz",
|
||||
"integrity": "sha512-qZit+moTXTyZFNDqSIR88/L3rdBlTU7CuW6XmyErD2FfHEkdoLgThkRbiQjzgYnX6rfgLx3Ci4eJmF4Ui5v1Cw==",
|
||||
"requires": {
|
||||
"@typescript-eslint/experimental-utils": "^2.5.0"
|
||||
}
|
||||
},
|
||||
"eslint-plugin-react": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz",
|
||||
"integrity": "sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ==",
|
||||
"version": "7.20.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.20.0.tgz",
|
||||
"integrity": "sha512-rqe1abd0vxMjmbPngo4NaYxTcR3Y4Hrmc/jg4T+sYz63yqlmJRknpEQfmWY+eDWPuMmix6iUIK+mv0zExjeLgA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-includes": "^3.1.1",
|
||||
@@ -7665,7 +7816,6 @@
|
||||
"object.values": "^1.1.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"resolve": "^1.15.1",
|
||||
"semver": "^6.3.0",
|
||||
"string.prototype.matchall": "^4.0.2",
|
||||
"xregexp": "^4.3.0"
|
||||
},
|
||||
@@ -7680,22 +7830,15 @@
|
||||
}
|
||||
},
|
||||
"object.entries": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz",
|
||||
"integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==",
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz",
|
||||
"integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"define-properties": "^1.1.3",
|
||||
"es-abstract": "^1.17.0-next.1",
|
||||
"function-bind": "^1.1.1",
|
||||
"es-abstract": "^1.17.5",
|
||||
"has": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -7722,20 +7865,26 @@
|
||||
"integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A=="
|
||||
},
|
||||
"espree": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz",
|
||||
"integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==",
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-7.1.0.tgz",
|
||||
"integrity": "sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"acorn": "^7.1.1",
|
||||
"acorn": "^7.2.0",
|
||||
"acorn-jsx": "^5.2.0",
|
||||
"eslint-visitor-keys": "^1.1.0"
|
||||
"eslint-visitor-keys": "^1.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz",
|
||||
"integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==",
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz",
|
||||
"integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==",
|
||||
"dev": true
|
||||
},
|
||||
"eslint-visitor-keys": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz",
|
||||
"integrity": "sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@@ -9707,16 +9856,10 @@
|
||||
"toidentifier": "1.0.0"
|
||||
}
|
||||
},
|
||||
"http-parser-js": {
|
||||
"version": "0.4.10",
|
||||
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz",
|
||||
"integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=",
|
||||
"dev": true
|
||||
},
|
||||
"http-proxy": {
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz",
|
||||
"integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==",
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
|
||||
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eventemitter3": "^4.0.0",
|
||||
@@ -9989,9 +10132,9 @@
|
||||
}
|
||||
},
|
||||
"inquirer": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz",
|
||||
"integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==",
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.2.0.tgz",
|
||||
"integrity": "sha512-E0c4rPwr9ByePfNlTIB8z51kK1s2n6jrHuJeEHENl/sbq2G/S1auvibgEwNR4uSyiU+PiYHqSwsgGiXjG8p5ZQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-escapes": "^4.2.1",
|
||||
@@ -11209,12 +11352,12 @@
|
||||
}
|
||||
},
|
||||
"jsx-ast-utils": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz",
|
||||
"integrity": "sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA==",
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz",
|
||||
"integrity": "sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-includes": "^3.0.3",
|
||||
"array-includes": "^3.1.1",
|
||||
"object.assign": "^4.1.0"
|
||||
}
|
||||
},
|
||||
@@ -15535,13 +15678,14 @@
|
||||
}
|
||||
},
|
||||
"sockjs": {
|
||||
"version": "0.3.19",
|
||||
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz",
|
||||
"integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==",
|
||||
"version": "0.3.20",
|
||||
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz",
|
||||
"integrity": "sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"faye-websocket": "^0.10.0",
|
||||
"uuid": "^3.0.1"
|
||||
"uuid": "^3.4.0",
|
||||
"websocket-driver": "0.6.5"
|
||||
}
|
||||
},
|
||||
"sockjs-client": {
|
||||
@@ -17314,9 +17458,9 @@
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
|
||||
},
|
||||
"v8-compile-cache": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
|
||||
"integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz",
|
||||
"integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
|
||||
"dev": true
|
||||
},
|
||||
"v8flags": {
|
||||
@@ -17983,9 +18127,9 @@
|
||||
}
|
||||
},
|
||||
"webpack-dev-server": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.9.0.tgz",
|
||||
"integrity": "sha512-E6uQ4kRrTX9URN9s/lIbqTAztwEPdvzVrcmHE8EQ9YnuT9J8Es5Wrd8n9BKg1a0oZ5EgEke/EQFgUsp18dSTBw==",
|
||||
"version": "3.11.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz",
|
||||
"integrity": "sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-html": "0.0.7",
|
||||
@@ -17996,31 +18140,31 @@
|
||||
"debug": "^4.1.1",
|
||||
"del": "^4.1.1",
|
||||
"express": "^4.17.1",
|
||||
"html-entities": "^1.2.1",
|
||||
"html-entities": "^1.3.1",
|
||||
"http-proxy-middleware": "0.19.1",
|
||||
"import-local": "^2.0.0",
|
||||
"internal-ip": "^4.3.0",
|
||||
"ip": "^1.1.5",
|
||||
"is-absolute-url": "^3.0.3",
|
||||
"killable": "^1.0.1",
|
||||
"loglevel": "^1.6.4",
|
||||
"loglevel": "^1.6.8",
|
||||
"opn": "^5.5.0",
|
||||
"p-retry": "^3.0.1",
|
||||
"portfinder": "^1.0.25",
|
||||
"portfinder": "^1.0.26",
|
||||
"schema-utils": "^1.0.0",
|
||||
"selfsigned": "^1.10.7",
|
||||
"semver": "^6.3.0",
|
||||
"serve-index": "^1.9.1",
|
||||
"sockjs": "0.3.19",
|
||||
"sockjs": "0.3.20",
|
||||
"sockjs-client": "1.4.0",
|
||||
"spdy": "^4.0.1",
|
||||
"spdy": "^4.0.2",
|
||||
"strip-ansi": "^3.0.1",
|
||||
"supports-color": "^6.1.0",
|
||||
"url": "^0.11.0",
|
||||
"webpack-dev-middleware": "^3.7.2",
|
||||
"webpack-log": "^2.0.0",
|
||||
"ws": "^6.2.1",
|
||||
"yargs": "12.0.5"
|
||||
"yargs": "^13.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
@@ -18029,72 +18173,6 @@
|
||||
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
|
||||
"dev": true
|
||||
},
|
||||
"cliui": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
|
||||
"integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "^2.1.1",
|
||||
"strip-ansi": "^4.0.0",
|
||||
"wrap-ansi": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||
"dev": true
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"get-caller-file": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
|
||||
"integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
|
||||
"dev": true
|
||||
},
|
||||
"invert-kv": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
|
||||
"integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
|
||||
"dev": true
|
||||
},
|
||||
"lcid": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
|
||||
"integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"invert-kv": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"os-locale": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
|
||||
"integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"execa": "^1.0.0",
|
||||
"lcid": "^2.0.0",
|
||||
"mem": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"require-main-filename": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
|
||||
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
|
||||
"dev": true
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
|
||||
@@ -18112,33 +18190,6 @@
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-fullwidth-code-point": "^2.0.0",
|
||||
"strip-ansi": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||
"dev": true
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
@@ -18157,38 +18208,6 @@
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
|
||||
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "^1.0.1",
|
||||
"strip-ansi": "^3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
||||
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
"strip-ansi": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
|
||||
@@ -18197,36 +18216,6 @@
|
||||
"requires": {
|
||||
"async-limiter": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"yargs": {
|
||||
"version": "12.0.5",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
|
||||
"integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cliui": "^4.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^3.0.0",
|
||||
"get-caller-file": "^1.0.1",
|
||||
"os-locale": "^3.0.0",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^1.0.1",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^2.0.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^3.2.1 || ^4.0.0",
|
||||
"yargs-parser": "^11.1.1"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "11.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
|
||||
"integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -18259,20 +18248,18 @@
|
||||
}
|
||||
},
|
||||
"websocket-driver": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz",
|
||||
"integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==",
|
||||
"version": "0.6.5",
|
||||
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz",
|
||||
"integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"http-parser-js": ">=0.4.0 <0.4.11",
|
||||
"safe-buffer": ">=5.1.0",
|
||||
"websocket-extensions": ">=0.1.1"
|
||||
}
|
||||
},
|
||||
"websocket-extensions": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
|
||||
"integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
|
||||
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
|
||||
"dev": true
|
||||
},
|
||||
"whatwg-encoding": {
|
||||
|
||||
14
package.json
14
package.json
@@ -4,7 +4,7 @@
|
||||
"description": "Cosmos Explorer",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@azure/cosmos": "3.6.3",
|
||||
"@azure/cosmos": "3.7.1",
|
||||
"@azure/cosmos-language-service": "0.0.4",
|
||||
"@jupyterlab/services": "4.2.0",
|
||||
"@jupyterlab/terminal": "1.2.1",
|
||||
@@ -50,7 +50,7 @@
|
||||
"dayjs": "1.8.19",
|
||||
"es6-object-assign": "1.1.0",
|
||||
"es6-symbol": "3.1.3",
|
||||
"eslint-plugin-jest": "23.8.2",
|
||||
"eslint-plugin-jest": "23.13.2",
|
||||
"hasher": "1.2.0",
|
||||
"immutable": "4.0.0-rc.12",
|
||||
"jquery": "3.4.0",
|
||||
@@ -110,8 +110,8 @@
|
||||
"@types/text-encoding": "0.0.33",
|
||||
"@types/underscore": "1.7.36",
|
||||
"@types/webfontloader": "1.6.29",
|
||||
"@typescript-eslint/eslint-plugin": "2.25.0",
|
||||
"@typescript-eslint/parser": "2.25.0",
|
||||
"@typescript-eslint/eslint-plugin": "3.2.0",
|
||||
"@typescript-eslint/parser": "3.2.0",
|
||||
"adal-angular": "1.0.15",
|
||||
"babel-jest": "24.9.0",
|
||||
"babel-loader": "8.1.0",
|
||||
@@ -123,9 +123,9 @@
|
||||
"enzyme": "3.10.0",
|
||||
"enzyme-adapter-react-16": "1.15.1",
|
||||
"enzyme-to-json": "3.4.3",
|
||||
"eslint": "6.8.0",
|
||||
"eslint": "7.2.0",
|
||||
"eslint-cli": "1.1.1",
|
||||
"eslint-plugin-react": "7.19.0",
|
||||
"eslint-plugin-react": "7.20.0",
|
||||
"expose-loader": "0.7.5",
|
||||
"file-loader": "2.0.0",
|
||||
"fs-extra": "7.0.0",
|
||||
@@ -155,7 +155,7 @@
|
||||
"webpack": "4.41.2",
|
||||
"webpack-bundle-analyzer": "3.6.1",
|
||||
"webpack-cli": "3.3.10",
|
||||
"webpack-dev-server": "3.9.0",
|
||||
"webpack-dev-server": "3.11.0",
|
||||
"worker-loader": "2.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -104,11 +104,9 @@ export class CapabilityNames {
|
||||
}
|
||||
|
||||
export class Features {
|
||||
public static readonly graphs = "graphs";
|
||||
public static readonly cosmosdb = "cosmosdb";
|
||||
public static readonly enableChangeFeedPolicy = "enablechangefeedpolicy";
|
||||
public static readonly enableRupm = "enablerupm";
|
||||
public static readonly cacheOptimizations = "dataexplorercacheoptimizations";
|
||||
public static readonly executeSproc = "dataexplorerexecutesproc";
|
||||
public static readonly hostedDataExplorer = "hosteddataexplorerenabled";
|
||||
public static readonly enableTtl = "enablettl";
|
||||
@@ -116,17 +114,14 @@ export class Features {
|
||||
public static readonly enableGallery = "enablegallery";
|
||||
public static readonly enableSpark = "enablespark";
|
||||
public static readonly livyEndpoint = "livyendpoint";
|
||||
public static readonly settingsPane = "dataexplorersettingspane";
|
||||
public static readonly throughputOverview = "throughputOverview";
|
||||
public static readonly enableNteract = "enablenteract";
|
||||
public static readonly notebookServerUrl = "notebookserverurl";
|
||||
public static readonly notebookServerToken = "notebookservertoken";
|
||||
public static readonly notebookBasePath = "notebookbasepath";
|
||||
public static readonly enableLegacyResourceTree = "enablelegacyresourcetree";
|
||||
public static readonly canExceedMaximumValue = "canexceedmaximumvalue";
|
||||
public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput";
|
||||
public static readonly enableAutoPilotV2 = "enableautopilotv2";
|
||||
public static readonly ttl90Days = "ttl90days";
|
||||
public static readonly enableRightPanelV2 = "enablerightpanelv2";
|
||||
}
|
||||
|
||||
export class AfecFeatures {
|
||||
|
||||
@@ -21,13 +21,15 @@ const _global = typeof self === "undefined" ? window : self;
|
||||
export const tokenProvider = async (requestInfo: RequestInfo) => {
|
||||
const { verb, resourceId, resourceType, headers } = requestInfo;
|
||||
if (config.platform === Platform.Emulator) {
|
||||
// TODO Remove any. SDK expects a return value for tokenProvider, but we are mutating the header object instead.
|
||||
return setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey) as any;
|
||||
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
|
||||
await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
|
||||
return decodeURIComponent(headers.authorization);
|
||||
}
|
||||
|
||||
if (_masterKey) {
|
||||
// TODO Remove any. SDK expects a return value for tokenProvider, but we are mutating the header object instead.
|
||||
return setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, _masterKey) as any;
|
||||
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
|
||||
await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
|
||||
return decodeURIComponent(headers.authorization);
|
||||
}
|
||||
|
||||
if (_resourceToken) {
|
||||
@@ -47,7 +49,9 @@ export const requestPlugin: Cosmos.Plugin<any> = async (requestContext, next) =>
|
||||
|
||||
export const endpoint = () => {
|
||||
if (config.platform === Platform.Emulator) {
|
||||
return config.EMULATOR_ENDPOINT || window.parent.location.origin;
|
||||
// In worker scope, _global(self).parent does not exist
|
||||
const location = _global.parent ? _global.parent.location : _global.location;
|
||||
return config.EMULATOR_ENDPOINT || location.origin;
|
||||
}
|
||||
return _endpoint || (_databaseAccount && _databaseAccount.properties && _databaseAccount.properties.documentEndpoint);
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import { SeverityLevel } from "@microsoft/applicationinsights-web";
|
||||
|
||||
// TODO: Move to a separate Diagnostics folder
|
||||
export class Logger {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
public static logInfo(message: string | Record<string, any>, area: string, code?: number): void {
|
||||
let logMessage: string;
|
||||
if (typeof message === "string") {
|
||||
|
||||
@@ -9,7 +9,6 @@ import { AccessibleVerticalList } from "../Explorer/Tree/AccessibleVerticalList"
|
||||
import { ArcadiaWorkspaceItem } from "../Explorer/Controls/Arcadia/ArcadiaMenuPicker";
|
||||
import { CassandraTableKey, CassandraTableKeys, TableDataClient } from "../Explorer/Tables/TableDataClient";
|
||||
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||
import { CommandButtonOptions } from "../Explorer/Controls/CommandButton/CommandButton";
|
||||
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { ExecuteSprocParam } from "../Explorer/Panes/ExecuteSprocParamsPane";
|
||||
import { GitHubClient } from "../GitHub/GitHubClient";
|
||||
@@ -27,6 +26,7 @@ import { Splitter } from "../Common/Splitter";
|
||||
import { StringInputPane } from "../Explorer/Panes/StringInputPane";
|
||||
import { TextFieldProps } from "../Explorer/Controls/DialogReactComponent/DialogComponent";
|
||||
import { UploadDetails } from "../workers/upload/definitions";
|
||||
import { UploadItemsPaneAdapter } from "../Explorer/Panes/UploadItemsPaneAdapter";
|
||||
|
||||
export interface ExplorerOptions {
|
||||
documentClientUtility: DocumentClientUtilityBase;
|
||||
@@ -86,7 +86,7 @@ export interface Explorer {
|
||||
isFeatureEnabled: (feature: string) => boolean;
|
||||
isGalleryEnabled: ko.Computed<boolean>;
|
||||
isGitHubPaneEnabled: ko.Observable<boolean>;
|
||||
isGraphsEnabled: ko.Computed<boolean>;
|
||||
isRightPanelV2Enabled: ko.Computed<boolean>;
|
||||
canExceedMaximumValue: ko.Computed<boolean>;
|
||||
hasAutoPilotV2FeatureFlag: ko.Computed<boolean>;
|
||||
isHostedDataExplorerEnabled: ko.Computed<boolean>;
|
||||
@@ -141,6 +141,7 @@ export interface Explorer {
|
||||
executeSprocParamsPane: ExecuteSprocParamsPane;
|
||||
renewAdHocAccessPane: RenewAdHocAccessPane;
|
||||
uploadItemsPane: UploadItemsPane;
|
||||
uploadItemsPaneAdapter: UploadItemsPaneAdapter;
|
||||
loadQueryPane: LoadQueryPane;
|
||||
saveQueryPane: ContextualPane;
|
||||
browseQueriesPane: BrowseQueriesPane;
|
||||
@@ -337,17 +338,6 @@ export interface Button {
|
||||
isSelected?: ko.Computed<boolean>;
|
||||
}
|
||||
|
||||
export interface CommandButton {
|
||||
disabled: ko.Subscribable<boolean>;
|
||||
visible: ko.Subscribable<boolean>;
|
||||
iconSrc: string;
|
||||
commandButtonLabel: string | ko.Observable<string>;
|
||||
tooltipText: string | ko.Observable<string>;
|
||||
children: ko.ObservableArray<CommandButtonOptions>;
|
||||
|
||||
commandClickCallback: () => void;
|
||||
}
|
||||
|
||||
export interface NotificationConsole {
|
||||
filteredConsoleData: ko.ObservableArray<ConsoleData>;
|
||||
isConsoleExpanded: ko.Observable<boolean>;
|
||||
@@ -371,7 +361,6 @@ export interface TreeNode {
|
||||
id: ko.Observable<string>;
|
||||
database?: Database;
|
||||
collection?: Collection;
|
||||
contextMenu?: ContextMenu;
|
||||
|
||||
onNewQueryClick?(source: any, event: MouseEvent): void;
|
||||
onNewStoredProcedureClick?(source: Collection, event: MouseEvent): void;
|
||||
@@ -536,7 +525,7 @@ export interface StoredProcedure extends TreeNode {
|
||||
id: ko.Observable<string>;
|
||||
body: ko.Observable<string>;
|
||||
|
||||
delete(source: TreeNode, event: MouseEvent | KeyboardEvent): void;
|
||||
delete(): void;
|
||||
open: () => void;
|
||||
select(): void;
|
||||
execute(params: string[], partitionKeyValue?: string): void;
|
||||
@@ -550,7 +539,7 @@ export interface UserDefinedFunction extends TreeNode {
|
||||
id: ko.Observable<string>;
|
||||
body: ko.Observable<string>;
|
||||
|
||||
delete(source: TreeNode, event: MouseEvent | KeyboardEvent): void;
|
||||
delete(): void;
|
||||
open: () => void;
|
||||
select(): void;
|
||||
}
|
||||
@@ -565,7 +554,7 @@ export interface Trigger extends TreeNode {
|
||||
triggerType: ko.Observable<string>;
|
||||
triggerOperation: ko.Observable<string>;
|
||||
|
||||
delete(source: TreeNode, event: MouseEvent | KeyboardEvent): void;
|
||||
delete(): void;
|
||||
open: () => void;
|
||||
select(): void;
|
||||
}
|
||||
@@ -1174,7 +1163,6 @@ export interface TriggerTab extends ScriptTab {
|
||||
}
|
||||
|
||||
export interface GraphTab extends Tab {}
|
||||
export interface NotebookTab extends Tab {}
|
||||
export interface EditorPosition {
|
||||
line: number;
|
||||
column: number;
|
||||
@@ -1218,7 +1206,7 @@ export enum CollectionTabKind {
|
||||
MongoShell = 10,
|
||||
DatabaseSettings = 11,
|
||||
Conflicts = 12,
|
||||
Notebook = 13,
|
||||
Notebook = 13 /* Deprecated */,
|
||||
Terminal = 14,
|
||||
NotebookV2 = 15,
|
||||
SparkMasterTab = 16,
|
||||
@@ -1232,17 +1220,6 @@ export enum TerminalKind {
|
||||
Cassandra = 2
|
||||
}
|
||||
|
||||
export interface ContextMenu {
|
||||
container: Explorer;
|
||||
visible: ko.Observable<boolean>;
|
||||
elementId: string;
|
||||
options: ko.ObservableArray<CommandButtonOptions>;
|
||||
tabIndex: ko.Observable<number>;
|
||||
|
||||
show(source: any, event: MouseEvent | KeyboardEvent): void;
|
||||
hide(source: any, event: MouseEvent | KeyboardEvent): void;
|
||||
}
|
||||
|
||||
export interface DataExplorerInputsFrame {
|
||||
databaseAccount: any;
|
||||
subscriptionId: string;
|
||||
|
||||
@@ -4,10 +4,6 @@ import * as ko from "knockout";
|
||||
import "./ComponentRegisterer";
|
||||
|
||||
describe("Component Registerer", () => {
|
||||
it("should register command-button component", () => {
|
||||
expect(ko.components.isRegistered("command-button")).toBe(true);
|
||||
});
|
||||
|
||||
it("should register input-typeahead component", () => {
|
||||
expect(ko.components.isRegistered("input-typeahead")).toBe(true);
|
||||
});
|
||||
@@ -64,10 +60,6 @@ describe("Component Registerer", () => {
|
||||
expect(ko.components.isRegistered("graph-tab")).toBe(true);
|
||||
});
|
||||
|
||||
it("should register notebook-tab component", () => {
|
||||
expect(ko.components.isRegistered("notebook-tab")).toBe(true);
|
||||
});
|
||||
|
||||
it("should register notebookv2-tab component", () => {
|
||||
expect(ko.components.isRegistered("notebookv2-tab")).toBe(true);
|
||||
});
|
||||
@@ -84,30 +76,6 @@ describe("Component Registerer", () => {
|
||||
expect(ko.components.isRegistered("mongo-shell-tab")).toBe(true);
|
||||
});
|
||||
|
||||
it("should register resource-tree component", () => {
|
||||
expect(ko.components.isRegistered("resource-tree")).toBe(true);
|
||||
});
|
||||
|
||||
it("should register database-node component", () => {
|
||||
expect(ko.components.isRegistered("database-node")).toBe(true);
|
||||
});
|
||||
|
||||
it("should register collection-node component", () => {
|
||||
expect(ko.components.isRegistered("collection-node")).toBe(true);
|
||||
});
|
||||
|
||||
it("should register stored-procedure-node component", () => {
|
||||
expect(ko.components.isRegistered("stored-procedure-node")).toBe(true);
|
||||
});
|
||||
|
||||
it("should register trigger-node component", () => {
|
||||
expect(ko.components.isRegistered("trigger-node")).toBe(true);
|
||||
});
|
||||
|
||||
it("should register user-defined-function-node component", () => {
|
||||
expect(ko.components.isRegistered("user-defined-function-node")).toBe(true);
|
||||
});
|
||||
|
||||
it("should registeradd-collection-pane component", () => {
|
||||
expect(ko.components.isRegistered("add-collection-pane")).toBe(true);
|
||||
});
|
||||
@@ -156,10 +124,6 @@ describe("Component Registerer", () => {
|
||||
expect(ko.components.isRegistered("manage-spark-cluster-pane")).toBe(true);
|
||||
});
|
||||
|
||||
it("should register collection-node-context-menu component", () => {
|
||||
expect(ko.components.isRegistered("collection-node-context-menu")).toBe(true);
|
||||
});
|
||||
|
||||
it("should register dynamic-list component", () => {
|
||||
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import * as ko from "knockout";
|
||||
import * as PaneComponents from "./Panes/PaneComponents";
|
||||
import * as TabComponents from "./Tabs/TabComponents";
|
||||
import * as TreeComponents from "./Tree/TreeComponents";
|
||||
import { CollapsiblePanelComponent } from "./Controls/CollapsiblePanel/CollapsiblePanelComponent";
|
||||
import { CommandButtonComponent } from "./Controls/CommandButton/CommandButton";
|
||||
import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent";
|
||||
import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent";
|
||||
import { EditorComponent } from "./Controls/Editor/EditorComponent";
|
||||
@@ -16,7 +14,6 @@ import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputI
|
||||
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
|
||||
import { ToolbarComponent } from "./Controls/Toolbar/Toolbar";
|
||||
|
||||
ko.components.register("command-button", CommandButtonComponent);
|
||||
ko.components.register("toolbar", new ToolbarComponent());
|
||||
ko.components.register("input-typeahead", new InputTypeaheadComponent());
|
||||
ko.components.register("new-vertex-form", NewVertexComponent);
|
||||
@@ -42,7 +39,6 @@ ko.components.register("tables-query-tab", new TabComponents.QueryTablesTab());
|
||||
ko.components.register("graph-tab", new TabComponents.GraphTab());
|
||||
ko.components.register("mongo-shell-tab", new TabComponents.MongoShellTab());
|
||||
ko.components.register("conflicts-tab", new TabComponents.ConflictsTab());
|
||||
ko.components.register("notebook-tab", new TabComponents.NotebookTab());
|
||||
ko.components.register("notebookv2-tab", new TabComponents.NotebookV2Tab());
|
||||
ko.components.register("terminal-tab", new TabComponents.TerminalTab());
|
||||
ko.components.register("spark-master-tab", new TabComponents.SparkMasterTab());
|
||||
@@ -52,14 +48,6 @@ ko.components.register("notebook-viewer-tab", new TabComponents.NotebookViewerTa
|
||||
// Database Tabs
|
||||
ko.components.register("database-settings-tab", new TabComponents.DatabaseSettingsTab());
|
||||
|
||||
// Resource Tree nodes
|
||||
ko.components.register("resource-tree", new TreeComponents.ResourceTree());
|
||||
ko.components.register("database-node", new TreeComponents.DatabaseTreeNode());
|
||||
ko.components.register("collection-node", new TreeComponents.CollectionTreeNode());
|
||||
ko.components.register("stored-procedure-node", new TreeComponents.StoredProcedureTreeNode());
|
||||
ko.components.register("trigger-node", new TreeComponents.TriggerTreeNode());
|
||||
ko.components.register("user-defined-function-node", new TreeComponents.UserDefinedFunctionTreeNode());
|
||||
|
||||
// Panes
|
||||
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
|
||||
ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent());
|
||||
@@ -93,6 +81,3 @@ ko.components.register("manage-spark-cluster-pane", new PaneComponents.ManageSpa
|
||||
ko.components.register("library-manage-pane", new PaneComponents.LibraryManagePaneComponent());
|
||||
ko.components.register("cluster-library-pane", new PaneComponents.ClusterLibraryPaneComponent());
|
||||
ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent());
|
||||
|
||||
// Menus
|
||||
ko.components.register("collection-node-context-menu", new TreeComponents.CollectionTreeNodeContextMenu());
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../Contracts/ViewModels";
|
||||
import { CommandButtonOptions } from "./Controls/CommandButton/CommandButton";
|
||||
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
|
||||
import AddCollectionIcon from "../../images/AddCollection.svg";
|
||||
import AddSqlQueryIcon from "../../images/AddSqlQuery_16x16.svg";
|
||||
@@ -115,7 +114,10 @@ export class ResourceTreeContextMenuButtonFactory {
|
||||
return items;
|
||||
}
|
||||
|
||||
public static createStoreProcedureContextMenuItems(container: ViewModels.Explorer): TreeNodeMenuItem[] {
|
||||
public static createStoreProcedureContextMenuItems(
|
||||
container: ViewModels.Explorer,
|
||||
storedProcedure: ViewModels.StoredProcedure
|
||||
): TreeNodeMenuItem[] {
|
||||
if (container.isPreferredApiCassandra()) {
|
||||
return [];
|
||||
}
|
||||
@@ -123,16 +125,16 @@ export class ResourceTreeContextMenuButtonFactory {
|
||||
return [
|
||||
{
|
||||
iconSrc: DeleteSprocIcon,
|
||||
onClick: () => {
|
||||
const selectedStoreProcedure: ViewModels.StoredProcedure = container.findSelectedStoredProcedure();
|
||||
selectedStoreProcedure && selectedStoreProcedure.delete(selectedStoreProcedure, null);
|
||||
},
|
||||
onClick: () => storedProcedure.delete(),
|
||||
label: "Delete Store Procedure"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
public static createTriggerContextMenuItems(container: ViewModels.Explorer): TreeNodeMenuItem[] {
|
||||
public static createTriggerContextMenuItems(
|
||||
container: ViewModels.Explorer,
|
||||
trigger: ViewModels.Trigger
|
||||
): TreeNodeMenuItem[] {
|
||||
if (container.isPreferredApiCassandra()) {
|
||||
return [];
|
||||
}
|
||||
@@ -140,16 +142,16 @@ export class ResourceTreeContextMenuButtonFactory {
|
||||
return [
|
||||
{
|
||||
iconSrc: DeleteTriggerIcon,
|
||||
onClick: () => {
|
||||
const selectedTrigger: ViewModels.Trigger = container.findSelectedTrigger();
|
||||
selectedTrigger && selectedTrigger.delete(selectedTrigger, null);
|
||||
},
|
||||
onClick: () => trigger.delete(),
|
||||
label: "Delete Trigger"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
public static createUserDefinedFunctionContextMenuItems(container: ViewModels.Explorer): TreeNodeMenuItem[] {
|
||||
public static createUserDefinedFunctionContextMenuItems(
|
||||
container: ViewModels.Explorer,
|
||||
userDefinedFunction: ViewModels.UserDefinedFunction
|
||||
): TreeNodeMenuItem[] {
|
||||
if (container.isPreferredApiCassandra()) {
|
||||
return [];
|
||||
}
|
||||
@@ -157,266 +159,9 @@ export class ResourceTreeContextMenuButtonFactory {
|
||||
return [
|
||||
{
|
||||
iconSrc: DeleteUDFIcon,
|
||||
onClick: () => {
|
||||
const selectedUDF: ViewModels.UserDefinedFunction = container.findSelectedUDF();
|
||||
selectedUDF && selectedUDF.delete(selectedUDF, null);
|
||||
},
|
||||
onClick: () => userDefinedFunction.delete(),
|
||||
label: "Delete User Defined Function"
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Current resource tree (in KO)
|
||||
* TODO: Remove when switching to new resource tree
|
||||
*/
|
||||
export class ContextMenuButtonFactory {
|
||||
public static createDatabaseContextMenuButton(
|
||||
container: ViewModels.Explorer,
|
||||
btnParams: DatabaseContextMenuButtonParams
|
||||
): CommandButtonOptions[] {
|
||||
const addCollectionId = `${btnParams.databaseId}-${container.addCollectionText()}`;
|
||||
const deleteDatabaseId = `${btnParams.databaseId}-${container.deleteDatabaseText()}`;
|
||||
const newCollectionButtonOptions: CommandButtonOptions = {
|
||||
iconSrc: AddCollectionIcon,
|
||||
id: addCollectionId,
|
||||
onCommandClick: () => {
|
||||
if (container.isPreferredApiCassandra()) {
|
||||
container.cassandraAddCollectionPane.open();
|
||||
} else {
|
||||
container.addCollectionPane.open(container.selectedDatabaseId());
|
||||
}
|
||||
|
||||
const selectedDatabase: ViewModels.Database = container.findSelectedDatabase();
|
||||
selectedDatabase && selectedDatabase.contextMenu.hide(selectedDatabase, null);
|
||||
},
|
||||
commandButtonLabel: container.addCollectionText(),
|
||||
hasPopup: true
|
||||
};
|
||||
|
||||
const deleteDatabaseButtonOptions: CommandButtonOptions = {
|
||||
iconSrc: DeleteDatabaseIcon,
|
||||
id: deleteDatabaseId,
|
||||
onCommandClick: () => {
|
||||
const database: ViewModels.Database = container.findSelectedDatabase();
|
||||
database.onDeleteDatabaseContextMenuClick(database, null);
|
||||
},
|
||||
commandButtonLabel: container.deleteDatabaseText(),
|
||||
hasPopup: true,
|
||||
disabled: ko.computed<boolean>(() => container.isNoneSelected()),
|
||||
visible: ko.computed<boolean>(() => !container.isNoneSelected())
|
||||
};
|
||||
|
||||
return [newCollectionButtonOptions, deleteDatabaseButtonOptions];
|
||||
}
|
||||
|
||||
public static createCollectionContextMenuButton(
|
||||
container: ViewModels.Explorer,
|
||||
btnParams: CollectionContextMenuButtonParams
|
||||
): CommandButtonOptions[] {
|
||||
const newSqlQueryId = `${btnParams.databaseId}-${btnParams.collectionId}-newSqlQuery`;
|
||||
const newSqlQueryForGraphId = `${btnParams.databaseId}-${btnParams.collectionId}-newSqlQueryForGraph`;
|
||||
const newQueryForMongoId = `${btnParams.databaseId}-${btnParams.collectionId}-newQuery`;
|
||||
const newShellForMongoId = `${btnParams.databaseId}-${btnParams.collectionId}-newShell`;
|
||||
const newStoredProcedureId = `${btnParams.databaseId}-${btnParams.collectionId}-newStoredProcedure`;
|
||||
const udfId = `${btnParams.databaseId}-${btnParams.collectionId}-udf`;
|
||||
const newTriggerId = `${btnParams.databaseId}-${btnParams.collectionId}-newTrigger`;
|
||||
const deleteCollectionId = `${btnParams.databaseId}-${btnParams.collectionId}-${container.deleteCollectionText()}`;
|
||||
|
||||
const newSQLQueryButtonOptions: CommandButtonOptions = {
|
||||
iconSrc: AddSqlQueryIcon,
|
||||
id: newSqlQueryId,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null);
|
||||
},
|
||||
commandButtonLabel: "New SQL Query",
|
||||
hasPopup: true,
|
||||
disabled: ko.computed<boolean>(
|
||||
() => container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiDocumentDB()
|
||||
),
|
||||
visible: ko.computed<boolean>(
|
||||
() => !container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiDocumentDB()
|
||||
)
|
||||
//TODO: Merge with add query logic below, same goes for CommandBarButtonFactory
|
||||
};
|
||||
|
||||
const newSQLQueryButtonOptionsForGraph: CommandButtonOptions = {
|
||||
iconSrc: AddSqlQueryIcon,
|
||||
id: newSqlQueryForGraphId,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null);
|
||||
},
|
||||
commandButtonLabel: "New SQL Query",
|
||||
hasPopup: true,
|
||||
disabled: ko.computed<boolean>(() => container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiGraph()),
|
||||
visible: ko.computed<boolean>(() => !container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiGraph())
|
||||
};
|
||||
|
||||
const newMongoQueryButtonOptions: CommandButtonOptions = {
|
||||
iconSrc: AddSqlQueryIcon,
|
||||
id: newQueryForMongoId,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null);
|
||||
},
|
||||
commandButtonLabel: "New Query",
|
||||
hasPopup: true,
|
||||
disabled: ko.computed<boolean>(
|
||||
() => container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()
|
||||
),
|
||||
visible: ko.computed<boolean>(
|
||||
() => !container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()
|
||||
)
|
||||
};
|
||||
|
||||
const newMongoShellButtonOptions: CommandButtonOptions = {
|
||||
iconSrc: HostedTerminalIcon,
|
||||
id: newShellForMongoId,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewMongoShellClick();
|
||||
},
|
||||
commandButtonLabel: "New Shell",
|
||||
hasPopup: true,
|
||||
disabled: ko.computed<boolean>(
|
||||
() => container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()
|
||||
),
|
||||
visible: ko.computed<boolean>(
|
||||
() => !container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()
|
||||
)
|
||||
};
|
||||
|
||||
const newStoredProcedureButtonOptions: CommandButtonOptions = {
|
||||
iconSrc: AddStoredProcedureIcon,
|
||||
id: newStoredProcedureId,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
|
||||
},
|
||||
commandButtonLabel: "New Stored Procedure",
|
||||
hasPopup: true,
|
||||
disabled: ko.computed<boolean>(() => container.isDatabaseNodeOrNoneSelected()),
|
||||
visible: ko.computed<boolean>(
|
||||
() => !container.isDatabaseNodeOrNoneSelected() && !container.isPreferredApiCassandra()
|
||||
)
|
||||
};
|
||||
|
||||
const newUserDefinedFunctionButtonOptions: CommandButtonOptions = {
|
||||
iconSrc: AddUdfIcon,
|
||||
id: udfId,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, null);
|
||||
},
|
||||
commandButtonLabel: "New UDF",
|
||||
hasPopup: true,
|
||||
disabled: ko.computed<boolean>(() => container.isDatabaseNodeOrNoneSelected()),
|
||||
visible: ko.computed<boolean>(
|
||||
() => !container.isDatabaseNodeOrNoneSelected() && !container.isPreferredApiCassandra()
|
||||
)
|
||||
};
|
||||
|
||||
const newTriggerButtonOptions: CommandButtonOptions = {
|
||||
iconSrc: AddTriggerIcon,
|
||||
id: newTriggerId,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, null);
|
||||
},
|
||||
commandButtonLabel: "New Trigger",
|
||||
hasPopup: true,
|
||||
disabled: ko.computed<boolean>(() => container.isDatabaseNodeOrNoneSelected()),
|
||||
visible: ko.computed<boolean>(
|
||||
() => !container.isDatabaseNodeOrNoneSelected() && !container.isPreferredApiCassandra()
|
||||
)
|
||||
};
|
||||
|
||||
const deleteCollectionButtonOptions: CommandButtonOptions = {
|
||||
iconSrc: DeleteCollectionIcon,
|
||||
id: deleteCollectionId,
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||
selectedCollection && selectedCollection.onDeleteCollectionContextMenuClick(selectedCollection, null);
|
||||
},
|
||||
commandButtonLabel: container.deleteCollectionText(),
|
||||
hasPopup: true,
|
||||
disabled: ko.computed<boolean>(() => container.isDatabaseNodeOrNoneSelected()),
|
||||
visible: ko.computed<boolean>(() => !container.isDatabaseNodeOrNoneSelected())
|
||||
//TODO: Change to isCollectionNodeorNoneSelected and same in CommandBarButtonFactory
|
||||
};
|
||||
|
||||
return [
|
||||
newSQLQueryButtonOptions,
|
||||
newSQLQueryButtonOptionsForGraph,
|
||||
newMongoQueryButtonOptions,
|
||||
newMongoShellButtonOptions,
|
||||
newStoredProcedureButtonOptions,
|
||||
newUserDefinedFunctionButtonOptions,
|
||||
newTriggerButtonOptions,
|
||||
deleteCollectionButtonOptions
|
||||
];
|
||||
}
|
||||
|
||||
public static createStoreProcedureContextMenuButton(container: ViewModels.Explorer): CommandButtonOptions[] {
|
||||
const deleteStoredProcedureId = "Context Menu - Delete Stored Procedure";
|
||||
const deleteStoreProcedureButtonOptions: CommandButtonOptions = {
|
||||
iconSrc: DeleteSprocIcon,
|
||||
id: deleteStoredProcedureId,
|
||||
onCommandClick: () => {
|
||||
const selectedStoreProcedure: ViewModels.StoredProcedure = container.findSelectedStoredProcedure();
|
||||
selectedStoreProcedure && selectedStoreProcedure.delete(selectedStoreProcedure, null);
|
||||
},
|
||||
commandButtonLabel: "Delete Stored Procedure",
|
||||
hasPopup: false,
|
||||
disabled: ko.computed<boolean>(() => container.isDatabaseNodeOrNoneSelected()),
|
||||
visible: ko.computed<boolean>(
|
||||
() => !container.isDatabaseNodeOrNoneSelected() && !container.isPreferredApiCassandra()
|
||||
)
|
||||
};
|
||||
|
||||
return [deleteStoreProcedureButtonOptions];
|
||||
}
|
||||
|
||||
public static createTriggerContextMenuButton(container: ViewModels.Explorer): CommandButtonOptions[] {
|
||||
const deleteTriggerId = "Context Menu - Delete Trigger";
|
||||
const deleteTriggerButtonOptions: CommandButtonOptions = {
|
||||
iconSrc: DeleteTriggerIcon,
|
||||
id: deleteTriggerId,
|
||||
onCommandClick: () => {
|
||||
const selectedTrigger: ViewModels.Trigger = container.findSelectedTrigger();
|
||||
selectedTrigger && selectedTrigger.delete(selectedTrigger, null);
|
||||
},
|
||||
commandButtonLabel: "Delete Trigger",
|
||||
hasPopup: false,
|
||||
disabled: ko.computed<boolean>(() => container.isDatabaseNodeOrNoneSelected()),
|
||||
visible: ko.computed<boolean>(
|
||||
() => !container.isDatabaseNodeOrNoneSelected() && !container.isPreferredApiCassandra()
|
||||
)
|
||||
};
|
||||
|
||||
return [deleteTriggerButtonOptions];
|
||||
}
|
||||
|
||||
public static createUserDefinedFunctionContextMenuButton(container: ViewModels.Explorer): CommandButtonOptions[] {
|
||||
const deleteUserDefinedFunctionId = "Context Menu - Delete User Defined Function";
|
||||
const deleteUserDefinedFunctionButtonOptions: CommandButtonOptions = {
|
||||
iconSrc: DeleteUDFIcon,
|
||||
id: deleteUserDefinedFunctionId,
|
||||
onCommandClick: () => {
|
||||
const selectedUDF: ViewModels.UserDefinedFunction = container.findSelectedUDF();
|
||||
selectedUDF && selectedUDF.delete(selectedUDF, null);
|
||||
},
|
||||
commandButtonLabel: "Delete User Defined Function",
|
||||
hasPopup: false,
|
||||
disabled: ko.computed<boolean>(() => container.isDatabaseNodeOrNoneSelected()),
|
||||
visible: ko.computed<boolean>(
|
||||
() => !container.isDatabaseNodeOrNoneSelected() && !container.isPreferredApiCassandra()
|
||||
)
|
||||
};
|
||||
|
||||
return [deleteUserDefinedFunctionButtonOptions];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
@import "../../../../less/Common/Constants";
|
||||
|
||||
@ButtonIconSize: 18px;
|
||||
|
||||
.commandBar {
|
||||
padding-left: @DefaultSpace;
|
||||
border-bottom: @ButtonBorderWidth solid @BaseMedium;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
height: @topcommandbarheight;
|
||||
|
||||
.staticCommands {
|
||||
list-style: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.overflowCommands {
|
||||
display:flex;
|
||||
flex: 1 0 auto;
|
||||
|
||||
.visibleCommands {
|
||||
display: inline-flex;
|
||||
list-style: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.partialSplitterContainer {
|
||||
padding: @SmallSpace @DefaultSpace @SmallSpace @SmallSpace;
|
||||
.flex-display();
|
||||
}
|
||||
}
|
||||
|
||||
.commandExpand {
|
||||
border: none;
|
||||
padding: 0px;
|
||||
direction: rtl;
|
||||
|
||||
&:hover {
|
||||
.hover();
|
||||
cursor: pointer;
|
||||
& > .commandDropdownContainer {
|
||||
display: block !important; // TODO: Remove after reusing KO mouseover and mouseout event handlers
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
.focus();
|
||||
}
|
||||
|
||||
.commandDropdownLauncher {
|
||||
direction: ltr;
|
||||
padding-top: @SmallSpace;
|
||||
|
||||
.commandIcon {
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.commandBarEllipses {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hiddenCommandsContainer > .commandDropdownLauncher {
|
||||
padding: 0px @DefaultSpace;
|
||||
}
|
||||
|
||||
.commandDropdownContainer {
|
||||
display: none;
|
||||
z-index: 1000;
|
||||
direction: ltr;
|
||||
position: absolute;
|
||||
width: fit-content;
|
||||
padding: 0px;
|
||||
background-color: @BaseLight;
|
||||
box-shadow: 1px 2px 6px @BaseMediumHigh, -2px 2px 6px @BaseMediumHigh;
|
||||
|
||||
.commandDropdown {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.feedbackButton {
|
||||
margin-right: @LargeSpace;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
command-button,
|
||||
.commandButtonReact {
|
||||
display: inline-flex;
|
||||
.commandButtonComponent {
|
||||
width: 100%;
|
||||
color: @BaseHigh;
|
||||
background-color: transparent;
|
||||
text-decoration: none;
|
||||
border: @ButtonBorderWidth solid transparent;
|
||||
.flex-display();
|
||||
|
||||
&:hover:not(.commandDisabled) {
|
||||
cursor: pointer;
|
||||
.hover();
|
||||
}
|
||||
|
||||
&:active:not(.commandDisabled) {
|
||||
border: @ButtonBorderWidth dashed @AccentMedium;
|
||||
.active();
|
||||
}
|
||||
|
||||
&:focus:not(.commandDisabled) {
|
||||
border: @ButtonBorderWidth dashed @AccentMedium;
|
||||
}
|
||||
|
||||
.commandContent {
|
||||
padding: @DefaultSpace @DefaultSpace @DefaultSpace;
|
||||
flex: 0 0 auto;
|
||||
|
||||
.commandIcon {
|
||||
margin: 0 @SmallSpace 0 0;
|
||||
vertical-align: text-top;
|
||||
width: @ButtonIconSize;
|
||||
height: @ButtonIconSize;
|
||||
}
|
||||
|
||||
.commandLabel {
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.commandContent .hasHiddenItems {
|
||||
padding-right: @SmallSpace;
|
||||
}
|
||||
}
|
||||
|
||||
.commandButtonComponent.commandDisabled {
|
||||
color: @BaseMediumHigh;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.commandExpand {
|
||||
padding-top: @SmallSpace;
|
||||
padding-bottom: @SmallSpace;
|
||||
&:hover {
|
||||
.hover();
|
||||
& > .commandDropdownContainer {
|
||||
display: block !important; // TODO: Remove after reusing KO mouseover and mouseout event handlers
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
.focus();
|
||||
}
|
||||
|
||||
.commandDropdownLauncher {
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
|
||||
.commandButtonComponent {
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.expandDropdown {
|
||||
padding: @SmallSpace;
|
||||
|
||||
img {
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
.partialSplitter {
|
||||
margin: @SmallSpace 0px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.commandButtonComponent[tabindex]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.selectedButton {
|
||||
background-color: @AccentLow;
|
||||
outline: none
|
||||
}
|
||||
}
|
||||
|
||||
.partialSplitter {
|
||||
border-left: @ButtonBorderWidth solid @BaseMediumHigh;
|
||||
}
|
||||
|
||||
.commandDropdown .commandButtonComponent {
|
||||
padding-left: 0px;
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
import * as ko from "knockout";
|
||||
import { CommandButtonComponent, CommandButtonOptions } from "./CommandButton";
|
||||
|
||||
const mockLabel = "Some Label";
|
||||
const id = "Some id";
|
||||
|
||||
function buildComponent(buttonOptions: any) {
|
||||
document.body.innerHTML = CommandButtonComponent.template as any;
|
||||
const vm = new CommandButtonComponent.viewModel(buttonOptions);
|
||||
ko.applyBindings(vm);
|
||||
}
|
||||
|
||||
describe("Command Button Component", () => {
|
||||
function buildButtonOptions(
|
||||
onClick: () => void,
|
||||
id?: string,
|
||||
label?: string,
|
||||
disabled?: ko.Observable<boolean>,
|
||||
visible?: ko.Observable<boolean>,
|
||||
tooltipText?: string
|
||||
): { buttonProps: CommandButtonOptions } {
|
||||
return {
|
||||
buttonProps: {
|
||||
iconSrc: "images/AddCollection.svg",
|
||||
id: id,
|
||||
commandButtonLabel: label || mockLabel,
|
||||
disabled: disabled,
|
||||
visible: visible,
|
||||
tooltipText: tooltipText,
|
||||
hasPopup: false,
|
||||
onCommandClick: onClick
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function buildSplitterButtonOptions(
|
||||
onClick: () => void,
|
||||
id?: string,
|
||||
label?: string,
|
||||
disabled?: ko.Observable<boolean>,
|
||||
visible?: ko.Observable<boolean>,
|
||||
tooltipText?: string
|
||||
): { buttonProps: CommandButtonOptions } {
|
||||
const child: CommandButtonOptions = {
|
||||
iconSrc: "images/settings_15x15.svg",
|
||||
id: id,
|
||||
commandButtonLabel: label || mockLabel,
|
||||
disabled: disabled,
|
||||
visible: visible,
|
||||
tooltipText: tooltipText,
|
||||
hasPopup: false,
|
||||
onCommandClick: onClick
|
||||
};
|
||||
|
||||
return {
|
||||
buttonProps: {
|
||||
iconSrc: "images/AddCollection.svg",
|
||||
id: id,
|
||||
commandButtonLabel: label || mockLabel,
|
||||
disabled: disabled,
|
||||
visible: visible,
|
||||
tooltipText: tooltipText,
|
||||
hasPopup: false,
|
||||
onCommandClick: onClick,
|
||||
children: [child]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
ko.cleanNode(document);
|
||||
document.body.innerHTML = "";
|
||||
});
|
||||
|
||||
describe("Rendering", () => {
|
||||
it("should display button label", () => {
|
||||
const buttonOptions = buildButtonOptions(() => {
|
||||
/** do nothing **/
|
||||
}, mockLabel);
|
||||
buildComponent(buttonOptions);
|
||||
expect(document.getElementsByClassName("commandButtonComponent").item(0).textContent).toContain(mockLabel);
|
||||
});
|
||||
|
||||
it("should display button icon", () => {
|
||||
const buttonOptions = buildButtonOptions(() => {
|
||||
/** do nothing **/
|
||||
});
|
||||
buildComponent(buttonOptions);
|
||||
expect(
|
||||
document
|
||||
.getElementsByTagName("img")
|
||||
.item(0)
|
||||
.getAttribute("src")
|
||||
).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Behavior", () => {
|
||||
let clickSpy: jasmine.Spy;
|
||||
|
||||
beforeEach(() => {
|
||||
clickSpy = jasmine.createSpy("Command button click spy");
|
||||
});
|
||||
|
||||
it("should trigger the click handler when the command button is clicked", () => {
|
||||
const buttonOptions = buildButtonOptions(() => clickSpy());
|
||||
buildComponent(buttonOptions);
|
||||
document
|
||||
.getElementsByClassName("commandButtonComponent")
|
||||
.item(0)
|
||||
.dispatchEvent(new Event("click"));
|
||||
expect(clickSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not trigger the click handler when command button is disabled", () => {
|
||||
const buttonOptions = buildButtonOptions(() => clickSpy(), id, mockLabel, ko.observable(true));
|
||||
buildComponent(buttonOptions);
|
||||
document
|
||||
.getElementsByClassName("commandButtonComponent")
|
||||
.item(0)
|
||||
.dispatchEvent(new Event("click"));
|
||||
expect(clickSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not have a dropdown if it has no child", () => {
|
||||
const buttonOptions = buildButtonOptions(() => clickSpy(), id, mockLabel, ko.observable(true));
|
||||
buildComponent(buttonOptions);
|
||||
const dropdownSize = document.getElementsByClassName("commandExpand").length;
|
||||
expect(dropdownSize).toBe(0);
|
||||
});
|
||||
|
||||
it("should have a dropdown if it has a child", () => {
|
||||
const buttonOptions = buildSplitterButtonOptions(() => clickSpy(), id, mockLabel, ko.observable(true));
|
||||
buildComponent(buttonOptions);
|
||||
const dropdownSize = document.getElementsByClassName("commandExpand").length;
|
||||
expect(dropdownSize).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,191 +0,0 @@
|
||||
/**
|
||||
* How to use this component:
|
||||
*
|
||||
* In your html markup, use:
|
||||
* <command-button params="{
|
||||
* iconSrc: '/icon/example/src/',
|
||||
* onCommandClick: () => { doSomething },
|
||||
* commandButtonLabel: 'Some Label'
|
||||
* disabled: true/false
|
||||
* }">
|
||||
* </command-button>
|
||||
*
|
||||
*/
|
||||
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
|
||||
import { KeyCodes } from "../../../Common/Constants";
|
||||
|
||||
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||
import template from "./command-button.html";
|
||||
|
||||
/**
|
||||
* Options for this component
|
||||
*/
|
||||
export interface CommandButtonOptions {
|
||||
/**
|
||||
* image source for the button icon
|
||||
*/
|
||||
iconSrc: string;
|
||||
|
||||
/**
|
||||
* Id for the button icon
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* Click handler for command button click
|
||||
*/
|
||||
onCommandClick: () => void;
|
||||
|
||||
/**
|
||||
* Label for the button
|
||||
*/
|
||||
commandButtonLabel: string | ko.Observable<string>;
|
||||
|
||||
/**
|
||||
* True if this button opens a tab or pane, false otherwise.
|
||||
*/
|
||||
hasPopup: boolean;
|
||||
|
||||
/**
|
||||
* Enabled/disabled state of command button
|
||||
*/
|
||||
disabled?: ko.Subscribable<boolean>;
|
||||
|
||||
/**
|
||||
* Visibility/Invisibility of the button
|
||||
*/
|
||||
visible?: ko.Subscribable<boolean>;
|
||||
|
||||
/**
|
||||
* Whether or not the button should have the 'selectedButton' styling
|
||||
*/
|
||||
isSelected?: ko.Observable<boolean>;
|
||||
|
||||
/**
|
||||
* Text to displayed in the tooltip on hover
|
||||
*/
|
||||
tooltipText?: string | ko.Observable<string>;
|
||||
|
||||
/**
|
||||
* Callback triggered when the template is bound to the component
|
||||
*/
|
||||
onTemplateReady?: () => void;
|
||||
|
||||
/**
|
||||
* tabindex for the command button
|
||||
*/
|
||||
tabIndex?: ko.Observable<number>;
|
||||
|
||||
/**
|
||||
* Childrens command buttons to hide in the dropdown
|
||||
*/
|
||||
children?: CommandButtonOptions[];
|
||||
}
|
||||
|
||||
export class CommandButtonViewModel extends WaitsForTemplateViewModel implements ViewModels.CommandButton {
|
||||
public commandClickCallback: () => void;
|
||||
public commandButtonId: string;
|
||||
public disabled: ko.Subscribable<boolean>;
|
||||
public visible: ko.Subscribable<boolean>;
|
||||
public isSelected: ko.Observable<boolean>;
|
||||
public iconSrc: string;
|
||||
public commandButtonLabel: ko.Observable<string>;
|
||||
public tooltipText: ko.Observable<string>;
|
||||
public tabIndex: ko.Observable<number>;
|
||||
public isTemplateReady: ko.Observable<boolean>;
|
||||
public hasPopup: boolean;
|
||||
public children: ko.ObservableArray<CommandButtonOptions>;
|
||||
|
||||
public constructor(options: { buttonProps: CommandButtonOptions }) {
|
||||
super();
|
||||
const props = options.buttonProps;
|
||||
const commandButtonLabel = props.commandButtonLabel;
|
||||
const tooltipText = props.tooltipText;
|
||||
this.commandButtonLabel =
|
||||
typeof commandButtonLabel === "string" ? ko.observable<string>(commandButtonLabel) : commandButtonLabel;
|
||||
this.commandButtonId = props.id;
|
||||
this.disabled = props.disabled || ko.observable(false);
|
||||
this.visible = props.visible || ko.observable(true);
|
||||
this.isSelected = props.isSelected || ko.observable(false);
|
||||
this.iconSrc = props.iconSrc;
|
||||
this.tabIndex = props.tabIndex || ko.observable(0);
|
||||
this.hasPopup = props.hasPopup;
|
||||
this.children = ko.observableArray(props.children);
|
||||
|
||||
super.onTemplateReady((isTemplateReady: boolean) => {
|
||||
if (isTemplateReady && props.onTemplateReady) {
|
||||
props.onTemplateReady();
|
||||
}
|
||||
});
|
||||
|
||||
if (tooltipText && typeof tooltipText === "string") {
|
||||
this.tooltipText = ko.observable<string>(tooltipText);
|
||||
} else if (tooltipText && typeof tooltipText === "function") {
|
||||
this.tooltipText = tooltipText;
|
||||
} else {
|
||||
this.tooltipText = this.commandButtonLabel;
|
||||
}
|
||||
|
||||
this.commandClickCallback = () => {
|
||||
if (this.disabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = document.querySelector(".commandDropdownContainer") as HTMLElement;
|
||||
if (el) {
|
||||
el.style.display = "none";
|
||||
}
|
||||
props.onCommandClick();
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
commandButtonClicked: this.commandButtonLabel
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
public onKeyPress(source: any, event: KeyboardEvent): boolean {
|
||||
if (event.keyCode === KeyCodes.Space || event.keyCode === KeyCodes.Enter) {
|
||||
this.commandClickCallback && this.commandClickCallback();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public onLauncherKeyDown(source: any, event: KeyboardEvent): boolean {
|
||||
// TODO: Convert JQuery code into Knockout
|
||||
if (event.keyCode === KeyCodes.DownArrow) {
|
||||
$(event.target)
|
||||
.parent()
|
||||
.siblings()
|
||||
.children(".commandExpand")
|
||||
.children(".commandDropdownContainer")
|
||||
.hide();
|
||||
$(event.target)
|
||||
.children(".commandDropdownContainer")
|
||||
.show()
|
||||
.focus();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
if (event.keyCode === KeyCodes.UpArrow) {
|
||||
$(event.target)
|
||||
.children(".commandDropdownContainer")
|
||||
.hide();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class for ko component registration
|
||||
*/
|
||||
export const CommandButtonComponent = {
|
||||
viewModel: CommandButtonViewModel,
|
||||
template
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
<span
|
||||
class="commandButtonComponent"
|
||||
role="menuitem"
|
||||
tabindex="0"
|
||||
data-bind="setTemplateReady: true,
|
||||
css: {
|
||||
commandDisabled: disabled,
|
||||
selectedButton: isSelected
|
||||
},
|
||||
event: {
|
||||
keypress: onKeyPress
|
||||
},
|
||||
attr: {
|
||||
title: tooltipText,
|
||||
id: commandButtonId,
|
||||
tabindex: tabIndex ,
|
||||
'aria-disabled': disabled,
|
||||
'aria-haspopup': hasPopup
|
||||
},
|
||||
click: commandClickCallback,
|
||||
visible: visible"
|
||||
>
|
||||
<div class="commandContent" data-bind="css: { hasHiddenItems: children().length > 0 }">
|
||||
<img class="commandIcon" data-bind="attr: {src: iconSrc, alt: commandButtonLabel}" />
|
||||
<span class="commandLabel" data-bind="text: commandButtonLabel"></span>
|
||||
</div>
|
||||
</span>
|
||||
<!-- ko if: children().length > 0 -->
|
||||
<div class="commandExpand" tabindex="0" data-bind="visible: visible, event: { keydown: onLauncherKeyDown }">
|
||||
<div class="commandDropdownLauncher">
|
||||
<span class="partialSplitter"></span>
|
||||
<span class="expandDropdown"> <img src="/QueryBuilder/CollapseChevronDown_16x.png" /> </span>
|
||||
</div>
|
||||
<div class="commandDropdownContainer">
|
||||
<div class="commandDropdown" data-bind="foreach: children">
|
||||
<command-button params="{buttonProps: $data}"></command-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /ko -->
|
||||
@@ -0,0 +1,20 @@
|
||||
.featurePanelComponentContainer {
|
||||
width: 800px;
|
||||
|
||||
.urlContainer {
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
white-space: nowrap;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.options {
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.checkboxRow {
|
||||
width: 390px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
|
||||
import { FeaturePanelComponent } from "./FeaturePanelComponent";
|
||||
|
||||
describe("Feature panel", () => {
|
||||
it("renders all flags", () => {
|
||||
const wrapper = shallow(<FeaturePanelComponent />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
252
src/Explorer/Controls/FeaturePanel/FeaturePanelComponent.tsx
Normal file
252
src/Explorer/Controls/FeaturePanel/FeaturePanelComponent.tsx
Normal file
@@ -0,0 +1,252 @@
|
||||
import * as React from "react";
|
||||
import { Stack } from "office-ui-fabric-react/lib/Stack";
|
||||
import { Dropdown, IDropdownOption, IDropdownStyles } from "office-ui-fabric-react/lib/Dropdown";
|
||||
import { Checkbox } from "office-ui-fabric-react/lib/Checkbox";
|
||||
import { TextField, ITextFieldStyles } from "office-ui-fabric-react/lib/TextField";
|
||||
import { DefaultButton } from "office-ui-fabric-react";
|
||||
import "./FeaturePanelComponent.less";
|
||||
|
||||
export const FeaturePanelComponent: React.FunctionComponent = () => {
|
||||
// Initial conditions
|
||||
const originalParams = new URLSearchParams(window.location.search);
|
||||
const urlParams = new Map(); // Params with lowercase keys
|
||||
originalParams.forEach((value: string, key: string) => urlParams.set(key.toLocaleLowerCase(), value));
|
||||
|
||||
const baseUrlOptions = [
|
||||
{ key: "https://localhost:1234/explorer.html", text: "localhost:1234" },
|
||||
{ key: "https://cosmos.azure.com/explorer.html", text: "cosmos.azure.com" },
|
||||
{ key: "https://portal.azure.com", text: "portal" }
|
||||
];
|
||||
|
||||
const platformOptions = [
|
||||
{ key: "Hosted", text: "Hosted" },
|
||||
{ key: "Portal", text: "Portal" },
|
||||
{ key: "Emulator", text: "Emulator" },
|
||||
{ key: "", text: "None" }
|
||||
];
|
||||
|
||||
// React hooks to keep state
|
||||
const [baseUrl, setBaseUrl] = React.useState<IDropdownOption>(
|
||||
baseUrlOptions.find(o => o.key === window.location.origin + window.location.pathname) || baseUrlOptions[0]
|
||||
);
|
||||
const [platform, setPlatform] = React.useState<IDropdownOption>(
|
||||
urlParams.has("platform")
|
||||
? platformOptions.find(o => o.key === urlParams.get("platform")) || platformOptions[0]
|
||||
: platformOptions[0]
|
||||
);
|
||||
|
||||
const booleanFeatures: {
|
||||
key: string;
|
||||
label: string;
|
||||
value: string;
|
||||
disabled?: () => boolean;
|
||||
reactState?: [boolean, React.Dispatch<React.SetStateAction<boolean>>];
|
||||
onChange?: (_?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean) => void;
|
||||
}[] = [
|
||||
{ key: "feature.enablechangefeedpolicy", label: "Enable change feed policy", value: "true" },
|
||||
{ key: "feature.enablerupm", label: "Enable RUPM", value: "true" },
|
||||
{ key: "feature.dataexplorerexecutesproc", label: "Execute stored procedure", value: "true" },
|
||||
{ key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
|
||||
{ key: "feature.enablettl", label: "Enable TTL", value: "true" },
|
||||
{ key: "feature.enablegallery", label: "Enable Notebook Gallery", value: "true" },
|
||||
{ key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" },
|
||||
{
|
||||
key: "feature.enablefixedcollectionwithsharedthroughput",
|
||||
label: "Enable fixed collection with shared throughput",
|
||||
value: "true"
|
||||
},
|
||||
{ key: "feature.ttl90days", label: "TTL 90 days", value: "true" },
|
||||
{ key: "feature.enablenotebooks", label: "Enable notebooks", value: "true" },
|
||||
{
|
||||
key: "feature.customportal",
|
||||
label: "Force Production portal (portal only)",
|
||||
value: "false",
|
||||
disabled: (): boolean => baseUrl.key !== "https://portal.azure.com"
|
||||
},
|
||||
{ key: "feature.enablespark", label: "Enable Synapse", value: "true" },
|
||||
{ key: "feature.enableautopilotv2", label: "Enable Auto-pilot V2", value: "true" }
|
||||
];
|
||||
|
||||
const stringFeatures: {
|
||||
key: string;
|
||||
label: string;
|
||||
placeholder: string;
|
||||
disabled?: () => boolean;
|
||||
reactState?: [string, React.Dispatch<React.SetStateAction<string>>];
|
||||
onChange?: (_: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => void;
|
||||
}[] = [
|
||||
{ key: "feature.notebookserverurl", label: "Notebook server URL", placeholder: "https://notebookserver" },
|
||||
{ key: "feature.notebookservertoken", label: "Notebook server token", placeholder: "" },
|
||||
{ key: "feature.notebookbasepath", label: "Notebook base path", placeholder: "" },
|
||||
{ key: "key", label: "Auth key", placeholder: "" },
|
||||
{
|
||||
key: "dataExplorerSource",
|
||||
label: "Data Explorer Source (portal only)",
|
||||
placeholder: "https://localhost:1234/explorer.html",
|
||||
disabled: (): boolean => baseUrl.key !== "https://portal.azure.com"
|
||||
},
|
||||
{ key: "feature.livyendpoint", label: "Livy endpoint", placeholder: "" }
|
||||
];
|
||||
|
||||
booleanFeatures.forEach(
|
||||
f => (f.reactState = React.useState<boolean>(urlParams.has(f.key) ? urlParams.get(f.key) === "true" : false))
|
||||
);
|
||||
stringFeatures.forEach(
|
||||
f => (f.reactState = React.useState<string>(urlParams.has(f.key) ? urlParams.get(f.key) : undefined))
|
||||
);
|
||||
|
||||
const buildUrl = (): string => {
|
||||
const fragments = (platform.key === "" ? [] : [`platform=${platform.key}`])
|
||||
.concat(booleanFeatures.map(f => (f.reactState[0] ? `${f.key}=${f.value}` : "")))
|
||||
.concat(stringFeatures.map(f => (f.reactState[0] ? `${f.key}=${encodeURIComponent(f.reactState[0])}` : "")))
|
||||
.filter(v => v && v.length > 0);
|
||||
|
||||
const paramString = fragments.length < 1 ? "" : `?${fragments.join("&")}`;
|
||||
return `${baseUrl.key}${paramString}`;
|
||||
};
|
||||
|
||||
const onChangeBaseUrl = (event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption): void => {
|
||||
setBaseUrl(option);
|
||||
};
|
||||
|
||||
const onChangePlatform = (event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption): void => {
|
||||
setPlatform(option);
|
||||
};
|
||||
|
||||
booleanFeatures.forEach(
|
||||
f =>
|
||||
(f.onChange = (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean): void => {
|
||||
f.reactState[1](checked);
|
||||
})
|
||||
);
|
||||
|
||||
stringFeatures.forEach(
|
||||
f =>
|
||||
(f.onChange = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string): void => {
|
||||
f.reactState[1](newValue);
|
||||
})
|
||||
);
|
||||
|
||||
const onNotebookShortcut = (): void => {
|
||||
booleanFeatures.find(f => f.key === "feature.enablenotebooks").reactState[1](true);
|
||||
stringFeatures
|
||||
.find(f => f.key === "feature.notebookserverurl")
|
||||
.reactState[1]("https://localhost:10001/12345/notebook/");
|
||||
stringFeatures.find(f => f.key === "feature.notebookservertoken").reactState[1]("token");
|
||||
stringFeatures.find(f => f.key === "feature.notebookbasepath").reactState[1](".");
|
||||
setPlatform(platformOptions.find(o => o.key === "Hosted"));
|
||||
};
|
||||
|
||||
const onPortalLocalDEShortcut = (): void => {
|
||||
setBaseUrl(baseUrlOptions.find(o => o.key === "https://portal.azure.com"));
|
||||
setPlatform(platformOptions.find(o => o.key === "Portal"));
|
||||
stringFeatures.find(f => f.key === "dataExplorerSource").reactState[1]("https://localhost:1234/explorer.html");
|
||||
};
|
||||
|
||||
const onReset = (): void => {
|
||||
booleanFeatures.forEach(f => f.reactState[1](false));
|
||||
stringFeatures.forEach(f => f.reactState[1](""));
|
||||
};
|
||||
|
||||
const stackTokens = { childrenGap: 10 };
|
||||
const dropdownStyles: Partial<IDropdownStyles> = { dropdown: { width: 200 } };
|
||||
const textFieldStyles: Partial<ITextFieldStyles> = { fieldGroup: { width: 300 } };
|
||||
|
||||
// Show in 2 columns to keep it compact
|
||||
let halfSize = Math.ceil(booleanFeatures.length / 2);
|
||||
const leftBooleanFeatures = booleanFeatures.slice(0, halfSize);
|
||||
const rightBooleanFeatures = booleanFeatures.slice(halfSize, booleanFeatures.length);
|
||||
|
||||
halfSize = Math.ceil(stringFeatures.length / 2);
|
||||
const leftStringFeatures = stringFeatures.slice(0, halfSize);
|
||||
const rightStringFeatures = stringFeatures.slice(halfSize, stringFeatures.length);
|
||||
|
||||
const anchorOptions = {
|
||||
href: buildUrl(),
|
||||
target: "_blank",
|
||||
rel: "noopener"
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="featurePanelComponentContainer">
|
||||
<div className="urlContainer">
|
||||
<a {...anchorOptions}>{buildUrl()}</a>
|
||||
</div>
|
||||
<Stack className="options" tokens={stackTokens}>
|
||||
<Stack horizontal horizontalAlign="space-between" tokens={stackTokens}>
|
||||
<DefaultButton onClick={onNotebookShortcut}>Notebooks on localhost</DefaultButton>
|
||||
<DefaultButton onClick={onPortalLocalDEShortcut}>Portal points to local DE</DefaultButton>
|
||||
<DefaultButton onClick={onReset}>Reset</DefaultButton>
|
||||
</Stack>
|
||||
<Stack horizontal horizontalAlign="start" tokens={stackTokens}>
|
||||
<Dropdown
|
||||
selectedKey={baseUrl.key}
|
||||
options={baseUrlOptions}
|
||||
onChange={onChangeBaseUrl}
|
||||
label="Base Url"
|
||||
styles={dropdownStyles}
|
||||
/>
|
||||
<Dropdown
|
||||
label="Platform"
|
||||
selectedKey={platform.key}
|
||||
onChange={onChangePlatform}
|
||||
options={platformOptions}
|
||||
styles={dropdownStyles}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack horizontal>
|
||||
<Stack className="checkboxRow" horizontalAlign="space-between">
|
||||
{leftBooleanFeatures.map(f => (
|
||||
<Checkbox
|
||||
key={f.key}
|
||||
label={f.label}
|
||||
checked={f.reactState[0]}
|
||||
onChange={f.onChange}
|
||||
disabled={f.disabled && f.disabled()}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
<Stack className="checkboxRow" horizontalAlign="space-between">
|
||||
{rightBooleanFeatures.map(f => (
|
||||
<Checkbox
|
||||
key={f.key}
|
||||
label={f.label}
|
||||
checked={f.reactState[0]}
|
||||
onChange={f.onChange}
|
||||
disabled={f.disabled && f.disabled()}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack horizontal tokens={stackTokens}>
|
||||
<Stack horizontalAlign="space-between">
|
||||
{leftStringFeatures.map(f => (
|
||||
<TextField
|
||||
key={f.key}
|
||||
value={f.reactState[0]}
|
||||
label={f.label}
|
||||
onChange={f.onChange}
|
||||
styles={textFieldStyles}
|
||||
placeholder={f.placeholder}
|
||||
disabled={f.disabled && f.disabled()}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
<Stack horizontalAlign="space-between">
|
||||
{rightStringFeatures.map(f => (
|
||||
<TextField
|
||||
key={f.key}
|
||||
value={f.reactState[0]}
|
||||
label={f.label}
|
||||
onChange={f.onChange}
|
||||
styles={textFieldStyles}
|
||||
placeholder={f.placeholder}
|
||||
disabled={f.disabled && f.disabled()}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
18
src/Explorer/Controls/FeaturePanel/FeaturePanelLauncher.less
Normal file
18
src/Explorer/Controls/FeaturePanel/FeaturePanelLauncher.less
Normal file
@@ -0,0 +1,18 @@
|
||||
.featurePanelLauncherContainer {
|
||||
.featurePanelLauncherModal {
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.ms-Dialog-main {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.activePatch {
|
||||
position: absolute;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
top: 20px;
|
||||
left: -20px;
|
||||
}
|
||||
86
src/Explorer/Controls/FeaturePanel/FeaturePanelLauncher.tsx
Normal file
86
src/Explorer/Controls/FeaturePanel/FeaturePanelLauncher.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import * as React from "react";
|
||||
import { FeaturePanelComponent } from "./FeaturePanelComponent";
|
||||
import { getTheme, mergeStyleSets, FontWeights, Modal, IconButton, IIconProps } from "office-ui-fabric-react";
|
||||
import "./FeaturePanelLauncher.less";
|
||||
|
||||
// Modal wrapper
|
||||
export const FeaturePanelLauncher: React.FunctionComponent = (): JSX.Element => {
|
||||
const [isModalOpen, showModal] = React.useState<boolean>(false);
|
||||
|
||||
const onActivate = (event: React.MouseEvent<HTMLSpanElement>): void => {
|
||||
if (!event.shiftKey || !event.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
event.stopPropagation();
|
||||
showModal(true);
|
||||
};
|
||||
|
||||
const theme = getTheme();
|
||||
const contentStyles = mergeStyleSets({
|
||||
container: {
|
||||
display: "flex",
|
||||
flexFlow: "column nowrap",
|
||||
alignItems: "stretch"
|
||||
},
|
||||
header: [
|
||||
// tslint:disable-next-line:deprecation
|
||||
theme.fonts.xLargePlus,
|
||||
{
|
||||
flex: "1 1 auto",
|
||||
borderTop: `4px solid ${theme.palette.themePrimary}`,
|
||||
color: theme.palette.neutralPrimary,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
fontWeight: FontWeights.semibold,
|
||||
padding: "12px 12px 14px 24px"
|
||||
}
|
||||
],
|
||||
body: {
|
||||
flex: "4 4 auto",
|
||||
overflowY: "hidden",
|
||||
marginBottom: 40,
|
||||
height: "100%",
|
||||
display: "flex"
|
||||
}
|
||||
});
|
||||
|
||||
const iconButtonStyles = {
|
||||
root: {
|
||||
color: theme.palette.neutralPrimary,
|
||||
marginLeft: "auto",
|
||||
marginTop: "4px",
|
||||
marginRight: "2px"
|
||||
},
|
||||
rootHovered: {
|
||||
color: theme.palette.neutralDark
|
||||
}
|
||||
};
|
||||
const cancelIcon: IIconProps = { iconName: "Cancel" };
|
||||
const hideModal = (): void => showModal(false);
|
||||
|
||||
return (
|
||||
<span className="activePatch" onDoubleClick={onActivate}>
|
||||
<Modal
|
||||
className="featurePanelLauncherContainer"
|
||||
titleAriaId="Features"
|
||||
isOpen={isModalOpen}
|
||||
onDismiss={hideModal}
|
||||
isBlocking={false}
|
||||
scrollableContentClassName="featurePanelLauncherModal"
|
||||
>
|
||||
<div className={contentStyles.header}>
|
||||
<span>Data Explorer Launcher</span>
|
||||
<IconButton
|
||||
styles={iconButtonStyles}
|
||||
iconProps={cancelIcon}
|
||||
ariaLabel="Close popup modal"
|
||||
onClick={hideModal}
|
||||
/>
|
||||
</div>
|
||||
<div className={contentStyles.body}>
|
||||
<FeaturePanelComponent />
|
||||
</div>
|
||||
</Modal>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,312 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Feature panel renders all flags 1`] = `
|
||||
<div
|
||||
className="featurePanelComponentContainer"
|
||||
>
|
||||
<div
|
||||
className="urlContainer"
|
||||
>
|
||||
<a
|
||||
href="https://localhost:1234/explorer.html?platform=Hosted"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
https://localhost:1234/explorer.html?platform=Hosted
|
||||
</a>
|
||||
</div>
|
||||
<Stack
|
||||
className="options"
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="space-between"
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
>
|
||||
Notebooks on localhost
|
||||
</CustomizedDefaultButton>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
>
|
||||
Portal points to local DE
|
||||
</CustomizedDefaultButton>
|
||||
<CustomizedDefaultButton
|
||||
onClick={[Function]}
|
||||
>
|
||||
Reset
|
||||
</CustomizedDefaultButton>
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
horizontalAlign="start"
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledWithResponsiveMode
|
||||
label="Base Url"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"key": "https://localhost:1234/explorer.html",
|
||||
"text": "localhost:1234",
|
||||
},
|
||||
Object {
|
||||
"key": "https://cosmos.azure.com/explorer.html",
|
||||
"text": "cosmos.azure.com",
|
||||
},
|
||||
Object {
|
||||
"key": "https://portal.azure.com",
|
||||
"text": "portal",
|
||||
},
|
||||
]
|
||||
}
|
||||
selectedKey="https://localhost:1234/explorer.html"
|
||||
styles={
|
||||
Object {
|
||||
"dropdown": Object {
|
||||
"width": 200,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<StyledWithResponsiveMode
|
||||
label="Platform"
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"key": "Hosted",
|
||||
"text": "Hosted",
|
||||
},
|
||||
Object {
|
||||
"key": "Portal",
|
||||
"text": "Portal",
|
||||
},
|
||||
Object {
|
||||
"key": "Emulator",
|
||||
"text": "Emulator",
|
||||
},
|
||||
Object {
|
||||
"key": "",
|
||||
"text": "None",
|
||||
},
|
||||
]
|
||||
}
|
||||
selectedKey="Hosted"
|
||||
styles={
|
||||
Object {
|
||||
"dropdown": Object {
|
||||
"width": 200,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
>
|
||||
<Stack
|
||||
className="checkboxRow"
|
||||
horizontalAlign="space-between"
|
||||
>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.enablechangefeedpolicy"
|
||||
label="Enable change feed policy"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.enablerupm"
|
||||
label="Enable RUPM"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.dataexplorerexecutesproc"
|
||||
label="Execute stored procedure"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.hosteddataexplorerenabled"
|
||||
label="Hosted Data Explorer (deprecated?)"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.enablettl"
|
||||
label="Enable TTL"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.enablegallery"
|
||||
label="Enable Notebook Gallery"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.canexceedmaximumvalue"
|
||||
label="Can exceed max value"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack
|
||||
className="checkboxRow"
|
||||
horizontalAlign="space-between"
|
||||
>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.enablefixedcollectionwithsharedthroughput"
|
||||
label="Enable fixed collection with shared throughput"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.ttl90days"
|
||||
label="TTL 90 days"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.enablenotebooks"
|
||||
label="Enable notebooks"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
disabled={true}
|
||||
key="feature.customportal"
|
||||
label="Force Production portal (portal only)"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.enablespark"
|
||||
label="Enable Synapse"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
<StyledCheckboxBase
|
||||
checked={false}
|
||||
key="feature.enableautopilotv2"
|
||||
label="Enable Auto-pilot V2"
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontal={true}
|
||||
tokens={
|
||||
Object {
|
||||
"childrenGap": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
horizontalAlign="space-between"
|
||||
>
|
||||
<StyledTextFieldBase
|
||||
key="feature.notebookserverurl"
|
||||
label="Notebook server URL"
|
||||
onChange={[Function]}
|
||||
placeholder="https://notebookserver"
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<StyledTextFieldBase
|
||||
key="feature.notebookservertoken"
|
||||
label="Notebook server token"
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<StyledTextFieldBase
|
||||
key="feature.notebookbasepath"
|
||||
label="Notebook base path"
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack
|
||||
horizontalAlign="space-between"
|
||||
>
|
||||
<StyledTextFieldBase
|
||||
key="key"
|
||||
label="Auth key"
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<StyledTextFieldBase
|
||||
disabled={true}
|
||||
key="dataExplorerSource"
|
||||
label="Data Explorer Source (portal only)"
|
||||
onChange={[Function]}
|
||||
placeholder="https://localhost:1234/explorer.html"
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<StyledTextFieldBase
|
||||
key="feature.livyendpoint"
|
||||
label="Livy endpoint"
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
styles={
|
||||
Object {
|
||||
"fieldGroup": Object {
|
||||
"width": 300,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,80 +0,0 @@
|
||||
/**
|
||||
* Message handler to communicate with NotebookApp iframe
|
||||
*/
|
||||
import Q from "q";
|
||||
import * as _ from "underscore";
|
||||
|
||||
import { HashMap } from "../../../Common/HashMap";
|
||||
import { CachedDataPromise } from "../../../Common/MessageHandler";
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import {
|
||||
MessageTypes,
|
||||
FromNotebookMessage,
|
||||
FromNotebookResponseMessage,
|
||||
FromDataExplorerMessage
|
||||
} from "../../../Terminal/NotebookAppContracts";
|
||||
|
||||
export class NotebookAppMessageHandler {
|
||||
private requestMap: HashMap<CachedDataPromise<any>>;
|
||||
|
||||
constructor(private targetIFrameWindow: Window) {
|
||||
this.requestMap = new HashMap();
|
||||
}
|
||||
|
||||
public handleCachedDataMessage(message: FromNotebookMessage): void {
|
||||
const messageContent = message && (message.message as FromNotebookResponseMessage);
|
||||
if (
|
||||
message == null ||
|
||||
messageContent == null ||
|
||||
messageContent.id == null ||
|
||||
!this.requestMap.has(messageContent.id)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cachedDataPromise = this.requestMap.get(messageContent.id);
|
||||
if (messageContent.error != null) {
|
||||
cachedDataPromise.deferred.reject(messageContent.error);
|
||||
} else {
|
||||
cachedDataPromise.deferred.resolve(messageContent.data);
|
||||
}
|
||||
this.runGarbageCollector();
|
||||
}
|
||||
|
||||
public sendCachedDataMessage<TResponseDataModel>(
|
||||
messageType: MessageTypes,
|
||||
params?: any
|
||||
): Q.Promise<TResponseDataModel> {
|
||||
let cachedDataPromise: CachedDataPromise<TResponseDataModel> = {
|
||||
deferred: Q.defer<TResponseDataModel>(),
|
||||
startTime: new Date(),
|
||||
id: _.uniqueId()
|
||||
};
|
||||
this.requestMap.set(cachedDataPromise.id, cachedDataPromise);
|
||||
this.sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
|
||||
|
||||
//TODO: Use telemetry to measure optimal time to resolve/reject promises
|
||||
return cachedDataPromise.deferred.promise.timeout(
|
||||
Constants.ClientDefaults.requestTimeoutMs,
|
||||
"Timed out while waiting for response from portal"
|
||||
);
|
||||
}
|
||||
|
||||
public sendMessage(data: FromDataExplorerMessage): void {
|
||||
if (!this.targetIFrameWindow) {
|
||||
console.error("targetIFrame not defined. This is not expected");
|
||||
return;
|
||||
}
|
||||
|
||||
this.targetIFrameWindow.postMessage(data, window.location.href || window.document.referrer);
|
||||
}
|
||||
|
||||
protected runGarbageCollector() {
|
||||
this.requestMap.keys().forEach((key: string) => {
|
||||
const promise: Q.Promise<any> = this.requestMap.get(key).deferred.promise;
|
||||
if (promise.isFulfilled() || promise.isRejected()) {
|
||||
this.requestMap.delete(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
GalleryViewerComponent,
|
||||
GalleryViewerComponentProps
|
||||
} from "./GalleryViewerComponent";
|
||||
import * as DataModels from "../../../Contracts/DataModels";
|
||||
|
||||
describe("GalleryCardsComponent", () => {
|
||||
it("renders", () => {
|
||||
@@ -18,14 +17,8 @@ describe("GalleryCardsComponent", () => {
|
||||
const props: GalleryCardsComponentProps = {
|
||||
data: [],
|
||||
userMetadata: undefined,
|
||||
onNotebookMetadataChange: (officialSamplesIndex: number, notebookMetadata: DataModels.NotebookMetadata) =>
|
||||
Promise.resolve(),
|
||||
onClick: (
|
||||
url: string,
|
||||
notebookMetadata: DataModels.NotebookMetadata,
|
||||
onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise<void>,
|
||||
isLikedNotebook: boolean
|
||||
) => Promise.resolve()
|
||||
onNotebookMetadataChange: () => Promise.resolve(),
|
||||
onClick: () => Promise.resolve()
|
||||
};
|
||||
|
||||
const wrapper = shallow(<GalleryCardsComponent {...props} />);
|
||||
@@ -39,12 +32,7 @@ describe("FullWidthTabs", () => {
|
||||
officialSamplesContent: [],
|
||||
likedNotebooksContent: [],
|
||||
userMetadata: undefined,
|
||||
onClick: (
|
||||
url: string,
|
||||
notebookMetadata: DataModels.NotebookMetadata,
|
||||
onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise<void>,
|
||||
isLikedNotebook: boolean
|
||||
) => Promise.resolve()
|
||||
onClick: () => Promise.resolve()
|
||||
};
|
||||
|
||||
const wrapper = shallow(<FullWidthTabs {...props} />);
|
||||
|
||||
@@ -39,7 +39,7 @@ export class GalleryCardsComponent extends React.Component<GalleryCardsComponent
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<Stack horizontal wrap tokens={this.sectionStackTokens}>
|
||||
{this.props.data.map((githubInfo: DataModels.GitHubInfoJunoResponse, index: any) => {
|
||||
{this.props.data.map((githubInfo: DataModels.GitHubInfoJunoResponse) => {
|
||||
const name = githubInfo.name;
|
||||
const url = githubInfo.downloadUrl;
|
||||
const notebookMetadata = githubInfo.metadata || {
|
||||
@@ -56,7 +56,7 @@ export class GalleryCardsComponent extends React.Component<GalleryCardsComponent
|
||||
const officialSamplesIndex = githubInfo.officialSamplesIndex;
|
||||
const isLikedNotebook = githubInfo.isLikedNotebook;
|
||||
const updateTabsStatePerNotebook = this.props.onNotebookMetadataChange
|
||||
? (notebookMetadata: DataModels.NotebookMetadata) =>
|
||||
? (notebookMetadata: DataModels.NotebookMetadata): Promise<void> =>
|
||||
this.props.onNotebookMetadataChange(officialSamplesIndex, notebookMetadata)
|
||||
: undefined;
|
||||
|
||||
@@ -68,7 +68,9 @@ export class GalleryCardsComponent extends React.Component<GalleryCardsComponent
|
||||
name={name}
|
||||
url={url}
|
||||
notebookMetadata={notebookMetadata}
|
||||
onClick={() => this.props.onClick(url, notebookMetadata, updateTabsStatePerNotebook, isLikedNotebook)}
|
||||
onClick={(): Promise<void> =>
|
||||
this.props.onClick(url, notebookMetadata, updateTabsStatePerNotebook, isLikedNotebook)
|
||||
}
|
||||
/>
|
||||
)
|
||||
);
|
||||
@@ -115,7 +117,7 @@ export class FullWidthTabs extends React.Component<FullWidthTabsProps, FullWidth
|
||||
title: "Official Samples",
|
||||
content: {
|
||||
className: "",
|
||||
render: () => (
|
||||
render: (): JSX.Element => (
|
||||
<GalleryCardsComponent
|
||||
data={this.state.officialSamplesContent}
|
||||
onClick={this.props.onClick}
|
||||
@@ -124,13 +126,13 @@ export class FullWidthTabs extends React.Component<FullWidthTabsProps, FullWidth
|
||||
/>
|
||||
)
|
||||
},
|
||||
isVisible: () => true
|
||||
isVisible: (): boolean => true
|
||||
},
|
||||
{
|
||||
title: "Liked Notebooks",
|
||||
content: {
|
||||
className: "",
|
||||
render: () => (
|
||||
render: (): JSX.Element => (
|
||||
<GalleryCardsComponent
|
||||
data={this.state.likedNotebooksContent}
|
||||
onClick={this.props.onClick}
|
||||
@@ -139,12 +141,15 @@ export class FullWidthTabs extends React.Component<FullWidthTabsProps, FullWidth
|
||||
/>
|
||||
)
|
||||
},
|
||||
isVisible: () => true
|
||||
isVisible: (): boolean => true
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
public updateTabsState = async (officialSamplesIndex: number, notebookMetadata: DataModels.NotebookMetadata) => {
|
||||
public updateTabsState = async (
|
||||
officialSamplesIndex: number,
|
||||
notebookMetadata: DataModels.NotebookMetadata
|
||||
): Promise<void> => {
|
||||
let currentLikedNotebooksContent = [...this.state.likedNotebooksContent];
|
||||
let currentUserMetadata = { ...this.state.userMetadata };
|
||||
let currentLikedNotebooks = [...currentUserMetadata.likedNotebooks];
|
||||
@@ -187,7 +192,7 @@ export class FullWidthTabs extends React.Component<FullWidthTabsProps, FullWidth
|
||||
});
|
||||
|
||||
JunoUtils.updateNotebookMetadata(this.authorizationToken, notebookMetadata).then(
|
||||
async returnedNotebookMetadata => {
|
||||
async () => {
|
||||
if (metadataLikesUpdates !== 0) {
|
||||
JunoUtils.updateUserMetadata(this.authorizationToken, currentUserMetadata);
|
||||
// TODO: update state here?
|
||||
@@ -203,9 +208,9 @@ export class FullWidthTabs extends React.Component<FullWidthTabsProps, FullWidth
|
||||
);
|
||||
};
|
||||
|
||||
private onTabIndexChange = (activeTabIndex: number) => this.setState({ activeTabIndex });
|
||||
private onTabIndexChange = (activeTabIndex: number): void => this.setState({ activeTabIndex });
|
||||
|
||||
public render() {
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<TabComponent.TabComponent
|
||||
tabs={this.appTabs}
|
||||
@@ -238,7 +243,7 @@ export class GalleryViewerContainerComponent extends React.Component<
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
componentDidMount(): void {
|
||||
const authToken = CosmosClient.authorizationToken();
|
||||
JunoUtils.getOfficialSampleNotebooks(authToken).then(
|
||||
(data1: DataModels.GitHubInfoJunoResponse[]) => {
|
||||
@@ -341,7 +346,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
|
||||
notebookMetadata: DataModels.NotebookMetadata,
|
||||
onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise<void>,
|
||||
isLikedNotebook: boolean
|
||||
) => {
|
||||
): Promise<void> => {
|
||||
if (!this.props.container) {
|
||||
SessionStorageUtility.setEntryString(
|
||||
StorageKey.NotebookMetadata,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
import { NotebookMetadataComponentProps, NotebookMetadataComponent } from "./NotebookMetadataComponent";
|
||||
import { NotebookMetadata } from "../../../Contracts/DataModels";
|
||||
|
||||
describe("NotebookMetadataComponent", () => {
|
||||
it("renders un-liked notebook", () => {
|
||||
@@ -10,7 +9,7 @@ describe("NotebookMetadataComponent", () => {
|
||||
container: undefined,
|
||||
notebookMetadata: undefined,
|
||||
notebookContent: {},
|
||||
onNotebookMetadataChange: (newNotebookMetadata: NotebookMetadata) => Promise.resolve(),
|
||||
onNotebookMetadataChange: () => Promise.resolve(),
|
||||
isLikedNotebook: false
|
||||
};
|
||||
|
||||
@@ -24,7 +23,7 @@ describe("NotebookMetadataComponent", () => {
|
||||
container: undefined,
|
||||
notebookMetadata: undefined,
|
||||
notebookContent: {},
|
||||
onNotebookMetadataChange: (newNotebookMetadata: NotebookMetadata) => Promise.resolve(),
|
||||
onNotebookMetadataChange: () => Promise.resolve(),
|
||||
isLikedNotebook: true
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@import "../../../../less/Common/Constants";
|
||||
|
||||
.tabComponentContainer {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
.flex-display();
|
||||
.flex-direction();
|
||||
|
||||
@@ -21,7 +21,6 @@ import EnvironmentUtility from "../Common/EnvironmentUtility";
|
||||
import GraphStylingPane from "./Panes/GraphStylingPane";
|
||||
import hasher from "hasher";
|
||||
import NewVertexPane from "./Panes/NewVertexPane";
|
||||
import NotebookTab from "./Tabs/NotebookTab";
|
||||
import NotebookV2Tab from "./Tabs/NotebookV2Tab";
|
||||
import Q from "q";
|
||||
import ResourceTokenCollection from "./Tree/ResourceTokenCollection";
|
||||
@@ -86,6 +85,7 @@ import { StringInputPane } from "./Panes/StringInputPane";
|
||||
import { TableColumnOptionsPane } from "./Panes/Tables/TableColumnOptionsPane";
|
||||
import { UploadFilePane } from "./Panes/UploadFilePane";
|
||||
import { UploadItemsPane } from "./Panes/UploadItemsPane";
|
||||
import { UploadItemsPaneAdapter } from "./Panes/UploadItemsPaneAdapter";
|
||||
|
||||
BindingHandlersRegisterer.registerBindingHandlers();
|
||||
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
|
||||
@@ -154,7 +154,6 @@ export default class Explorer implements ViewModels.Explorer {
|
||||
public selectedNode: ko.Observable<ViewModels.TreeNode>;
|
||||
public isRefreshingExplorer: ko.Observable<boolean>;
|
||||
private resourceTree: ResourceTreeAdapter;
|
||||
private enableLegacyResourceTree: ko.Observable<boolean>;
|
||||
|
||||
// Resource Token
|
||||
public resourceTokenDatabaseId: ko.Observable<string>;
|
||||
@@ -188,6 +187,7 @@ export default class Explorer implements ViewModels.Explorer {
|
||||
public executeSprocParamsPane: ViewModels.ExecuteSprocParamsPane;
|
||||
public renewAdHocAccessPane: ViewModels.RenewAdHocAccessPane;
|
||||
public uploadItemsPane: ViewModels.UploadItemsPane;
|
||||
public uploadItemsPaneAdapter: UploadItemsPaneAdapter;
|
||||
public loadQueryPane: ViewModels.LoadQueryPane;
|
||||
public saveQueryPane: ViewModels.ContextualPane;
|
||||
public browseQueriesPane: ViewModels.BrowseQueriesPane;
|
||||
@@ -203,8 +203,8 @@ export default class Explorer implements ViewModels.Explorer {
|
||||
// features
|
||||
public isGalleryEnabled: ko.Computed<boolean>;
|
||||
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
||||
public isGraphsEnabled: ko.Computed<boolean>;
|
||||
public isHostedDataExplorerEnabled: ko.Computed<boolean>;
|
||||
public isRightPanelV2Enabled: ko.Computed<boolean>;
|
||||
public canExceedMaximumValue: ko.Computed<boolean>;
|
||||
public hasAutoPilotV2FeatureFlag: ko.Computed<boolean>;
|
||||
|
||||
@@ -303,7 +303,7 @@ export default class Explorer implements ViewModels.Explorer {
|
||||
this.openedTabs &&
|
||||
this.openedTabs().forEach(tab => {
|
||||
if (tab.tabKind === ViewModels.CollectionTabKind.Notebook) {
|
||||
(tab as NotebookTab).reconfigureServiceEndpoints();
|
||||
throw new Error("NotebookTab is deprecated. Use NotebookV2Tab");
|
||||
} else if (tab.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
|
||||
(tab as NotebookV2Tab).reconfigureServiceEndpoints();
|
||||
}
|
||||
@@ -381,7 +381,6 @@ export default class Explorer implements ViewModels.Explorer {
|
||||
this.armEndpoint = ko.observable<string>(undefined);
|
||||
this.queriesClient = new QueriesClient(this);
|
||||
this.isTryCosmosDBSubscription = ko.observable<boolean>(false);
|
||||
this.enableLegacyResourceTree = ko.observable<boolean>(false);
|
||||
|
||||
this.resourceTokenDatabaseId = ko.observable<string>();
|
||||
this.resourceTokenCollectionId = ko.observable<string>();
|
||||
@@ -411,9 +410,6 @@ export default class Explorer implements ViewModels.Explorer {
|
||||
this.shouldShowContextSwitchPrompt = ko.observable<boolean>(false);
|
||||
this.isGalleryEnabled = ko.computed<boolean>(() => this.isFeatureEnabled(Constants.Features.enableGallery));
|
||||
this.isGitHubPaneEnabled = ko.observable<boolean>(false);
|
||||
this.isGraphsEnabled = ko.computed<boolean>(() => {
|
||||
return this.isFeatureEnabled(Constants.Features.graphs);
|
||||
});
|
||||
|
||||
this.canExceedMaximumValue = ko.computed<boolean>(() =>
|
||||
this.isFeatureEnabled(Constants.Features.canExceedMaximumValue)
|
||||
@@ -551,6 +547,9 @@ export default class Explorer implements ViewModels.Explorer {
|
||||
!this.isRunningOnNationalCloud() &&
|
||||
!this.isPreferredApiGraph()
|
||||
);
|
||||
this.isRightPanelV2Enabled = ko.computed<boolean>(() =>
|
||||
this.isFeatureEnabled(Constants.Features.enableRightPanelV2)
|
||||
);
|
||||
this.defaultExperience.subscribe((defaultExperience: string) => {
|
||||
if (
|
||||
defaultExperience &&
|
||||
@@ -707,6 +706,8 @@ export default class Explorer implements ViewModels.Explorer {
|
||||
container: this
|
||||
});
|
||||
|
||||
this.uploadItemsPaneAdapter = new UploadItemsPaneAdapter(this);
|
||||
|
||||
this.loadQueryPane = new LoadQueryPane({
|
||||
documentClientUtility: this.documentClientUtility,
|
||||
id: "loadquerypane",
|
||||
@@ -1100,8 +1101,6 @@ export default class Explorer implements ViewModels.Explorer {
|
||||
this.sparkClusterConnectionInfo.valueHasMutated();
|
||||
}
|
||||
|
||||
this.enableLegacyResourceTree(this.isFeatureEnabled(Constants.Features.enableLegacyResourceTree));
|
||||
|
||||
featureSubcription.dispose();
|
||||
});
|
||||
|
||||
@@ -2768,7 +2767,7 @@ export default class Explorer implements ViewModels.Explorer {
|
||||
const openedNotebookTabs = this.openedTabs().filter(
|
||||
(tab: ViewModels.Tab) =>
|
||||
tab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
|
||||
(tab as NotebookTab).notebookPath() === notebookFile.path
|
||||
(tab as NotebookV2Tab).notebookPath() === notebookFile.path
|
||||
);
|
||||
if (openedNotebookTabs.length > 0) {
|
||||
this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
|
||||
@@ -2792,12 +2791,12 @@ export default class Explorer implements ViewModels.Explorer {
|
||||
.filter(
|
||||
(tab: ViewModels.Tab) =>
|
||||
tab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
|
||||
FileSystemUtil.isPathEqual((tab as NotebookTab).notebookPath(), originalPath)
|
||||
FileSystemUtil.isPathEqual((tab as NotebookV2Tab).notebookPath(), originalPath)
|
||||
)
|
||||
.forEach(tab => {
|
||||
tab.tabTitle(newNotebookFile.name);
|
||||
tab.tabPath(newNotebookFile.path);
|
||||
(tab as NotebookTab).notebookPath(newNotebookFile.path);
|
||||
(tab as NotebookV2Tab).notebookPath(newNotebookFile.path);
|
||||
});
|
||||
|
||||
return newNotebookFile;
|
||||
@@ -3035,7 +3034,7 @@ export default class Explorer implements ViewModels.Explorer {
|
||||
// Don't delete if tab is open to avoid accidental deletion
|
||||
const openedNotebookTabs = this.openedTabs().filter(
|
||||
(tab: ViewModels.Tab) =>
|
||||
tab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && (tab as NotebookTab).notebookPath() === item.path
|
||||
tab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && (tab as NotebookV2Tab).notebookPath() === item.path
|
||||
);
|
||||
if (openedNotebookTabs.length > 0) {
|
||||
this.showOkModalDialog("Unable to delete file", "This file is being edited. Please close the tab and try again.");
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { CommandButtonOptions } from "./../Controls/CommandButton/CommandButton";
|
||||
|
||||
export default class ContextMenu implements ViewModels.ContextMenu {
|
||||
public container: ViewModels.Explorer;
|
||||
public visible: ko.Observable<boolean>;
|
||||
public elementId: string;
|
||||
public options: ko.ObservableArray<CommandButtonOptions>;
|
||||
public tabIndex: ko.Observable<number>;
|
||||
|
||||
constructor(container: ViewModels.Explorer, rid: string) {
|
||||
this.container = container;
|
||||
this.visible = ko.observable<boolean>(false);
|
||||
this.elementId = `contextMenu${rid}`;
|
||||
this.options = ko.observableArray<CommandButtonOptions>([]);
|
||||
this.tabIndex = ko.observable<number>(0);
|
||||
}
|
||||
|
||||
public show(source: ViewModels.TreeNode, event: MouseEvent) {
|
||||
if (source && source.contextMenu && source.contextMenu.visible && source.contextMenu.visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.container.selectedNode(source);
|
||||
const elementId = source.contextMenu.elementId;
|
||||
const htmlElement = document.getElementById(elementId);
|
||||
htmlElement.style.left = `${event.clientX}px`;
|
||||
htmlElement.style.top = `${event.clientY}px`;
|
||||
|
||||
!!source.contextMenu && source.contextMenu.visible(true);
|
||||
source.contextMenu.tabIndex(0);
|
||||
htmlElement.focus();
|
||||
}
|
||||
|
||||
public hide(source: ViewModels.TreeNode, event: MouseEvent) {
|
||||
if (!source || !source.contextMenu || !source.contextMenu.visible || !source.contextMenu.visible()) {
|
||||
return;
|
||||
}
|
||||
source.contextMenu.tabIndex(-1);
|
||||
source.contextMenu.visible(false);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import { TableColumnOptionsPane } from "../../src/Explorer/Panes/Tables/TableCol
|
||||
import { TextFieldProps } from "./Controls/DialogReactComponent/DialogComponent";
|
||||
import { UploadDetails } from "../workers/upload/definitions";
|
||||
import { UploadFilePane } from "./Panes/UploadFilePane";
|
||||
import { UploadItemsPaneAdapter } from "./Panes/UploadItemsPaneAdapter";
|
||||
import { Versions } from "../../src/Contracts/ExplorerContracts";
|
||||
import { CollectionCreationDefaults } from "../Shared/Constants";
|
||||
|
||||
@@ -86,6 +87,7 @@ export class ExplorerStub implements ViewModels.Explorer {
|
||||
public settingsPane: ViewModels.SettingsPane;
|
||||
public executeSprocParamsPane: ViewModels.ExecuteSprocParamsPane;
|
||||
public uploadItemsPane: ViewModels.UploadItemsPane;
|
||||
public uploadItemsPaneAdapter: UploadItemsPaneAdapter;
|
||||
public loadQueryPane: ViewModels.LoadQueryPane;
|
||||
public saveQueryPane: ViewModels.ContextualPane;
|
||||
public browseQueriesPane: ViewModels.BrowseQueriesPane;
|
||||
@@ -96,7 +98,7 @@ export class ExplorerStub implements ViewModels.Explorer {
|
||||
public manageSparkClusterPane: ViewModels.ContextualPane;
|
||||
public isGalleryEnabled: ko.Computed<boolean>;
|
||||
public isGitHubPaneEnabled: ko.Observable<boolean>;
|
||||
public isGraphsEnabled: ko.Computed<boolean>;
|
||||
public isRightPanelV2Enabled: ko.Computed<boolean>;
|
||||
public canExceedMaximumValue: ko.Computed<boolean>;
|
||||
public isHostedDataExplorerEnabled: ko.Computed<boolean>;
|
||||
public parentFrameDataExplorerVersion: ko.Observable<string> = ko.observable<string>(Versions.DataExplorer);
|
||||
@@ -447,7 +449,6 @@ export class DatabaseStub implements ViewModels.Database {
|
||||
public collections: ko.ObservableArray<ViewModels.Collection>;
|
||||
public isDatabaseExpanded: ko.Observable<boolean>;
|
||||
public isDatabaseShared: ko.Computed<boolean>;
|
||||
public contextMenu: ViewModels.ContextMenu;
|
||||
public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
|
||||
public offer: ko.Observable<DataModels.Offer>;
|
||||
|
||||
@@ -459,7 +460,6 @@ export class DatabaseStub implements ViewModels.Database {
|
||||
this.id = options.id;
|
||||
this.collections = options.collections;
|
||||
this.isDatabaseExpanded = options.isDatabaseExpanded;
|
||||
this.contextMenu = options.contextMenu;
|
||||
this.offer = options.offer;
|
||||
this.selectedSubnodeKind = options.selectedSubnodeKind;
|
||||
}
|
||||
@@ -562,8 +562,6 @@ export class CollectionStub implements ViewModels.Collection {
|
||||
public storedProceduresFocused: ko.Observable<boolean>;
|
||||
public userDefinedFunctionsFocused: ko.Observable<boolean>;
|
||||
public triggersFocused: ko.Observable<boolean>;
|
||||
public contextMenu: ViewModels.ContextMenu;
|
||||
public documentsContextMenu: ViewModels.ContextMenu;
|
||||
public conflictResolutionPolicy: ko.Observable<DataModels.ConflictResolutionPolicy>;
|
||||
public changeFeedPolicy: ko.Observable<DataModels.ChangeFeedPolicy>;
|
||||
public geospatialConfig: ko.Observable<DataModels.GeospatialConfig>;
|
||||
@@ -608,69 +606,8 @@ export class CollectionStub implements ViewModels.Collection {
|
||||
this.storedProceduresFocused = options.storedProceduresFocused;
|
||||
this.userDefinedFunctionsFocused = options.userDefinedFunctionsFocused;
|
||||
this.triggersFocused = options.triggersFocused;
|
||||
this.contextMenu = options.contextMenu;
|
||||
this.documentsContextMenu = options.documentsContextMenu;
|
||||
}
|
||||
|
||||
public onKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
throw new Error("Not implemented");
|
||||
};
|
||||
|
||||
public onKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
throw new Error("Not implemented");
|
||||
};
|
||||
|
||||
public onMenuKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
throw new Error("Not implemented");
|
||||
};
|
||||
|
||||
public onDocumentDBDocumentsKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
throw new Error("Not implemented");
|
||||
};
|
||||
|
||||
public onDocumentDBDocumentsKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
throw new Error("Not implemented");
|
||||
};
|
||||
|
||||
public onMongoDBDocumentsKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
throw new Error("Not implemented");
|
||||
};
|
||||
|
||||
public onMongoDBDocumentsKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
throw new Error("Not implemented");
|
||||
};
|
||||
|
||||
public onSettingsKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
throw new Error("Not implemented");
|
||||
};
|
||||
|
||||
public onSettingsKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
throw new Error("Not implemented");
|
||||
};
|
||||
|
||||
public onStoredProceduresKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
throw new Error("Not implemented");
|
||||
};
|
||||
|
||||
public onStoredProceduresKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
throw new Error("Not implemented");
|
||||
};
|
||||
|
||||
public onUserDefinedFunctionsKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
throw new Error("Not implemented");
|
||||
};
|
||||
|
||||
public onUserDefinedFunctionsKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
throw new Error("Not implemented");
|
||||
};
|
||||
|
||||
public onTriggersKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
throw new Error("Not implemented");
|
||||
};
|
||||
|
||||
public onTriggersKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
throw new Error("Not implemented");
|
||||
};
|
||||
public expandCollapseCollection() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ import Explorer from "../Explorer";
|
||||
import ko from "knockout";
|
||||
import { AutopilotTier } from "../../Contracts/DataModels";
|
||||
|
||||
jest.mock("../Tabs/NotebookTab");
|
||||
|
||||
describe("Add Collection Pane", () => {
|
||||
describe("isValid()", () => {
|
||||
let explorer: ViewModels.Explorer;
|
||||
|
||||
@@ -2,8 +2,6 @@ import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import Explorer from "../Explorer";
|
||||
import AddDatabasePane from "./AddDatabasePane";
|
||||
|
||||
jest.mock("../Tabs/NotebookTab");
|
||||
|
||||
describe("Add Database Pane", () => {
|
||||
describe("getSharedThroughputDefault()", () => {
|
||||
let explorer: ViewModels.Explorer;
|
||||
|
||||
146
src/Explorer/Panes/GenericRightPaneComponent.tsx
Normal file
146
src/Explorer/Panes/GenericRightPaneComponent.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import * as React from "react";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { IconButton, PrimaryButton } from "office-ui-fabric-react/lib/Button";
|
||||
import { KeyCodes } from "../../Common/Constants";
|
||||
import { Subscription } from "knockout";
|
||||
import ErrorRedIcon from "../../../images/error_red.svg";
|
||||
import LoadingIndicatorIcon from "../../../images/LoadingIndicator_3Squares.gif";
|
||||
|
||||
export interface GenericRightPaneProps {
|
||||
container: ViewModels.Explorer;
|
||||
content: JSX.Element;
|
||||
formError: string;
|
||||
formErrorDetail: string;
|
||||
id: string;
|
||||
isExecuting: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: () => void;
|
||||
submitButtonText: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface GenericRightPaneState {
|
||||
panelHeight: number;
|
||||
}
|
||||
|
||||
export class GenericRightPaneComponent extends React.Component<GenericRightPaneProps, GenericRightPaneState> {
|
||||
private notificationConsoleSubscription: Subscription;
|
||||
|
||||
constructor(props: GenericRightPaneProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
panelHeight: this.getPanelHeight()
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount(): void {
|
||||
this.notificationConsoleSubscription = this.props.container.isNotificationConsoleExpanded.subscribe(() => {
|
||||
this.setState({ panelHeight: this.getPanelHeight() });
|
||||
});
|
||||
this.props.container.isNotificationConsoleExpanded.extend({ rateLimit: 10 });
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
this.notificationConsoleSubscription && this.notificationConsoleSubscription.dispose();
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div tabIndex={-1} onKeyDown={this.onKeyDown}>
|
||||
<div className="contextual-pane-out" onClick={this.props.onClose}></div>
|
||||
<div
|
||||
className="contextual-pane"
|
||||
id={this.props.id}
|
||||
style={{ height: this.state.panelHeight }}
|
||||
onKeyDown={this.onKeyDown}
|
||||
>
|
||||
<div className="panelContentWrapper">
|
||||
{this.createPanelHeader()}
|
||||
{this.createErrorSection()}
|
||||
{this.props.content}
|
||||
{this.createPanelFooter()}
|
||||
</div>
|
||||
{this.createLoadingScreen()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private createPanelHeader = (): JSX.Element => {
|
||||
return (
|
||||
<div className="firstdivbg headerline">
|
||||
<span id="databaseTitle">{this.props.title}</span>
|
||||
<IconButton
|
||||
ariaLabel="Close pane"
|
||||
title="Close pane"
|
||||
onClick={this.props.onClose}
|
||||
tabIndex={0}
|
||||
className="closePaneBtn"
|
||||
iconProps={{ iconName: "Cancel" }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
private createErrorSection = (): JSX.Element => {
|
||||
return (
|
||||
<div className="warningErrorContainer" aria-live="assertive" hidden={!this.props.formError}>
|
||||
<div className="warningErrorContent">
|
||||
<span>
|
||||
<img className="paneErrorIcon" src={ErrorRedIcon} alt="Error" />
|
||||
</span>
|
||||
<span className="warningErrorDetailsLinkContainer">
|
||||
<span className="formErrors" title={this.props.formError}>
|
||||
{this.props.formError}
|
||||
</span>
|
||||
<a className="errorLink" role="link" hidden={!this.props.formErrorDetail} onClick={this.showErrorDetail}>
|
||||
More details
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
private createPanelFooter = (): JSX.Element => {
|
||||
return (
|
||||
<div className="paneFooter">
|
||||
<div className="leftpanel-okbut">
|
||||
<PrimaryButton
|
||||
ariaLabel="Submit"
|
||||
title="Submit"
|
||||
onClick={this.props.onSubmit}
|
||||
tabIndex={0}
|
||||
className="genericPaneSubmitBtn"
|
||||
text={this.props.submitButtonText}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
private createLoadingScreen = (): JSX.Element => {
|
||||
return (
|
||||
<div className="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" hidden={!this.props.isExecuting}>
|
||||
<img className="dataExplorerLoader" src={LoadingIndicatorIcon} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
private onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
|
||||
if (event.keyCode === KeyCodes.Escape) {
|
||||
this.props.onClose();
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
private showErrorDetail = (): void => {
|
||||
this.props.container.expandConsole();
|
||||
};
|
||||
|
||||
private getPanelHeight = (): number => {
|
||||
const notificationConsoleElement: HTMLElement = document.getElementById("explorerNotificationConsole");
|
||||
return window.innerHeight - $(notificationConsoleElement).height();
|
||||
};
|
||||
}
|
||||
@@ -2,8 +2,6 @@ import * as Constants from "../../Common/Constants";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import Explorer from "../Explorer";
|
||||
|
||||
jest.mock("../Tabs/NotebookTab");
|
||||
|
||||
describe("Settings Pane", () => {
|
||||
describe("shouldShowQueryPageOptions()", () => {
|
||||
let explorer: ViewModels.Explorer;
|
||||
|
||||
238
src/Explorer/Panes/UploadItemsPaneAdapter.tsx
Normal file
238
src/Explorer/Panes/UploadItemsPaneAdapter.tsx
Normal file
@@ -0,0 +1,238 @@
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
|
||||
import * as ko from "knockout";
|
||||
import * as React from "react";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { IconButton } from "office-ui-fabric-react/lib/Button";
|
||||
import { GenericRightPaneComponent, GenericRightPaneProps } from "./GenericRightPaneComponent";
|
||||
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
|
||||
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
||||
import { UploadDetailsRecord, UploadDetails } from "../../workers/upload/definitions";
|
||||
import InfoBubbleIcon from "../../../images/info-bubble.svg";
|
||||
|
||||
const UPLOAD_FILE_SIZE_LIMIT = 2097152;
|
||||
|
||||
export class UploadItemsPaneAdapter implements ReactAdapter {
|
||||
public parameters: ko.Observable<number>;
|
||||
private isOpened: boolean;
|
||||
private isExecuting: boolean;
|
||||
private formError: string;
|
||||
private formErrorDetail: string;
|
||||
private selectedFiles: FileList;
|
||||
private selectedFilesTitle: string;
|
||||
private uploadFileData: UploadDetailsRecord[];
|
||||
|
||||
public constructor(private container: ViewModels.Explorer) {
|
||||
this.parameters = ko.observable(Date.now());
|
||||
this.reset();
|
||||
this.triggerRender();
|
||||
}
|
||||
|
||||
public renderComponent(): JSX.Element {
|
||||
if (!this.isOpened) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const props: GenericRightPaneProps = {
|
||||
container: this.container,
|
||||
content: this.createContent(),
|
||||
formError: this.formError,
|
||||
formErrorDetail: this.formErrorDetail,
|
||||
id: "uploaditemspane",
|
||||
isExecuting: this.isExecuting,
|
||||
title: "Upload Items",
|
||||
submitButtonText: "Upload",
|
||||
onClose: () => this.close(),
|
||||
onSubmit: () => this.submit()
|
||||
};
|
||||
return <GenericRightPaneComponent {...props} />;
|
||||
}
|
||||
|
||||
public triggerRender(): void {
|
||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
||||
}
|
||||
|
||||
public open(): void {
|
||||
this.isOpened = true;
|
||||
this.triggerRender();
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.reset();
|
||||
this.triggerRender();
|
||||
}
|
||||
|
||||
public submit(): void {
|
||||
this.formError = "";
|
||||
if (!this.selectedFiles || this.selectedFiles.length === 0) {
|
||||
this.formError = "No files specified";
|
||||
this.formErrorDetail = "No files were specified. Please input at least one file.";
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
"Could not upload items -- No files were specified. Please input at least one file."
|
||||
);
|
||||
this.triggerRender();
|
||||
return;
|
||||
} else if (this._totalFileSizeForFileList() > UPLOAD_FILE_SIZE_LIMIT) {
|
||||
this.formError = "Upload file size limit exceeded";
|
||||
this.formErrorDetail = "Total file upload size exceeds the 2 MB file size limit.";
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Error,
|
||||
"Could not upload items -- Total file upload size exceeds the 2 MB file size limit."
|
||||
);
|
||||
this.triggerRender();
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedCollection: ViewModels.Collection = this.container.findSelectedCollection();
|
||||
this.isExecuting = true;
|
||||
this.triggerRender();
|
||||
selectedCollection &&
|
||||
selectedCollection
|
||||
.uploadFiles(this.selectedFiles)
|
||||
.then(
|
||||
(uploadDetails: UploadDetails) => {
|
||||
this.uploadFileData = uploadDetails.data;
|
||||
this.selectedFiles = undefined;
|
||||
this.selectedFilesTitle = "";
|
||||
},
|
||||
error => {
|
||||
const message = ErrorParserUtility.parse(error);
|
||||
this.formError = message[0].message;
|
||||
this.formErrorDetail = message[0].message;
|
||||
}
|
||||
)
|
||||
.finally(() => {
|
||||
this.triggerRender();
|
||||
this.isExecuting = false;
|
||||
});
|
||||
}
|
||||
|
||||
private createContent = (): JSX.Element => {
|
||||
return <div className="panelContent">{this.createMainContentSection()}</div>;
|
||||
};
|
||||
|
||||
private createMainContentSection = (): JSX.Element => {
|
||||
return (
|
||||
<div className="paneMainContent">
|
||||
<div className="renewUploadItemsHeader">
|
||||
<span> Select JSON Files </span>
|
||||
<span className="infoTooltip" role="tooltip" tabIndex={0}>
|
||||
<img className="infoImg" src={InfoBubbleIcon} alt="More information" />
|
||||
<span className="tooltiptext infoTooltipWidth">
|
||||
Select one or more JSON files to upload. Each file can contain a single JSON document or an array of JSON
|
||||
documents. The combined size of all files in an individual upload operation must be less than 2 MB. You
|
||||
can perform multiple upload operations for larger data sets.
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
className="importFilesTitle"
|
||||
type="text"
|
||||
disabled
|
||||
value={this.selectedFilesTitle}
|
||||
aria-label="Select JSON Files"
|
||||
/>
|
||||
<input
|
||||
type="file"
|
||||
id="importDocsInput"
|
||||
title="Upload Icon"
|
||||
multiple
|
||||
accept="application/json"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
style={{ display: "none" }}
|
||||
onChange={this.updateSelectedFiles}
|
||||
/>
|
||||
<IconButton
|
||||
iconProps={{ iconName: "FolderHorizontal" }}
|
||||
className="fileImportButton"
|
||||
alt="Select JSON files to upload"
|
||||
title="Select JSON files to upload"
|
||||
onClick={this.onImportButtonClick}
|
||||
onKeyPress={this.onImportButtonKeyPress}
|
||||
/>
|
||||
<div className="fileUploadSummaryContainer" hidden={this.uploadFileData.length === 0}>
|
||||
<b>File upload status</b>
|
||||
<table className="fileUploadSummary">
|
||||
<thead>
|
||||
<tr className="fileUploadSummaryHeader fileUploadSummaryTuple">
|
||||
<th>FILE NAME</th>
|
||||
<th>STATUS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.uploadFileData.map(
|
||||
(data: UploadDetailsRecord): JSX.Element => {
|
||||
return (
|
||||
<tr className="fileUploadSummaryTuple" key={data.fileName}>
|
||||
<td>{data.fileName}</td>
|
||||
<td>{this.fileUploadSummaryText(data.numSucceeded, data.numFailed)}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
private updateSelectedFiles = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
this.selectedFiles = event.target.files;
|
||||
this._updateSelectedFilesTitle();
|
||||
this.triggerRender();
|
||||
};
|
||||
|
||||
private _updateSelectedFilesTitle = (): void => {
|
||||
this.selectedFilesTitle = "";
|
||||
|
||||
if (!this.selectedFiles || this.selectedFiles.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.selectedFiles.length; i++) {
|
||||
this.selectedFilesTitle += `"${this.selectedFiles.item(i).name}"`;
|
||||
}
|
||||
};
|
||||
|
||||
private _totalFileSizeForFileList(): number {
|
||||
let totalFileSize = 0;
|
||||
if (!this.selectedFiles) {
|
||||
return totalFileSize;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.selectedFiles.length; i++) {
|
||||
totalFileSize += this.selectedFiles.item(i).size;
|
||||
}
|
||||
|
||||
return totalFileSize;
|
||||
}
|
||||
|
||||
private fileUploadSummaryText = (numSucceeded: number, numFailed: number): string => {
|
||||
return `${numSucceeded} items created, ${numFailed} errors`;
|
||||
};
|
||||
|
||||
private onImportButtonClick = (): void => {
|
||||
document.getElementById("importDocsInput").click();
|
||||
};
|
||||
|
||||
private onImportButtonKeyPress = (event: React.KeyboardEvent<HTMLButtonElement>): void => {
|
||||
if (event.charCode === Constants.KeyCodes.Enter || event.charCode === Constants.KeyCodes.Space) {
|
||||
this.onImportButtonClick();
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
private reset = (): void => {
|
||||
this.isOpened = false;
|
||||
this.isExecuting = false;
|
||||
this.formError = "";
|
||||
this.formErrorDetail = "";
|
||||
this.selectedFiles = undefined;
|
||||
this.selectedFilesTitle = "";
|
||||
this.uploadFileData = [];
|
||||
};
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
}
|
||||
|
||||
>.title {
|
||||
position: relative; // To attach FeaturePanelLauncher as absolute
|
||||
color: @BaseHigh;
|
||||
font-size: 48px;
|
||||
padding-left: 0px;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import * as React from "react";
|
||||
import * as Constants from "../../Common/Constants";
|
||||
import { Link } from "office-ui-fabric-react/lib/Link";
|
||||
import { FeaturePanelLauncher } from "../Controls/FeaturePanel/FeaturePanelLauncher";
|
||||
|
||||
export interface SplashScreenItem {
|
||||
iconSrc: string;
|
||||
@@ -29,7 +30,10 @@ export class SplashScreenComponent extends React.Component<SplashScreenComponent
|
||||
return (
|
||||
<div className="splashScreenContainer">
|
||||
<div className="splashScreen">
|
||||
<div className="title">Welcome to Cosmos DB</div>
|
||||
<div className="title">
|
||||
Welcome to Cosmos DB
|
||||
<FeaturePanelLauncher />
|
||||
</div>
|
||||
<div className="subtitle">Globally distributed, multi-model database service for any scale</div>
|
||||
<div className="mainButtonsContainer">
|
||||
{this.props.mainItems.map((item: SplashScreenItem) => (
|
||||
|
||||
@@ -6,8 +6,6 @@ import { DataAccessUtility } from "../../Platform/Portal/DataAccessUtility";
|
||||
import Explorer from "../Explorer";
|
||||
import DocumentClientUtilityBase from "../../Common/DocumentClientUtilityBase";
|
||||
|
||||
jest.mock("./NotebookTab");
|
||||
|
||||
describe("Documents tab", () => {
|
||||
describe("buildQuery", () => {
|
||||
it("should generate the right select query for SQL API", () => {
|
||||
|
||||
@@ -965,7 +965,10 @@ export default class DocumentsTab extends TabsBase implements ViewModels.Documen
|
||||
onCommandClick: () => {
|
||||
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
|
||||
const focusElement = document.getElementById("itemImportLink");
|
||||
selectedCollection && container.uploadItemsPane.open();
|
||||
const uploadItemsPane = container.isRightPanelV2Enabled()
|
||||
? container.uploadItemsPaneAdapter
|
||||
: container.uploadItemsPane;
|
||||
selectedCollection && uploadItemsPane.open();
|
||||
focusElement && focusElement.focus();
|
||||
},
|
||||
commandButtonLabel: label,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<div style="width: 100%; height: 100%; margin-left: 3px;" data-bind="attr: { id: tabId }">
|
||||
<!-- This runs the NotebookApp hosted by DataExplorer -->
|
||||
<iframe
|
||||
style="width:100%; height: 100%; border:none"
|
||||
data-bind="setTemplateReady: true, attr: { id: notebookContainerId, src: notebookAppIFrameSrc }"
|
||||
></iframe>
|
||||
</div>
|
||||
@@ -1,539 +0,0 @@
|
||||
import * as Q from "q";
|
||||
import * as ko from "knockout";
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import TabsBase from "./TabsBase";
|
||||
|
||||
import NewCellIcon from "../../../images/notebook/Notebook-insert-cell.svg";
|
||||
import CutIcon from "../../../images/notebook/Notebook-cut.svg";
|
||||
import CopyIcon from "../../../images/notebook/Notebook-copy.svg";
|
||||
import PasteIcon from "../../../images/notebook/Notebook-paste.svg";
|
||||
import RunIcon from "../../../images/notebook/Notebook-run.svg";
|
||||
import RunAllIcon from "../../../images/notebook/Notebook-run-all.svg";
|
||||
import RestartIcon from "../../../images/notebook/Notebook-restart.svg";
|
||||
import SaveIcon from "../../../images/save-cosmos.svg";
|
||||
import ClearAllOutputsIcon from "../../../images/notebook/Notebook-clear-all-outputs.svg";
|
||||
import UndoIcon from "../../../images/notebook/Notebook-undo.svg";
|
||||
import RedoIcon from "../../../images/notebook/Notebook-redo.svg";
|
||||
import { CommandBarComponentButtonFactory } from "../Menus/CommandBar/CommandBarComponentButtonFactory";
|
||||
import { NotebookAppMessageHandler } from "../Controls/Notebook/NotebookAppMessageHandler";
|
||||
import * as NotebookAppContracts from "../../Terminal/NotebookAppContracts";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
|
||||
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
|
||||
import { NotebookTerminalComponent } from "../Controls/Notebook/NotebookTerminalComponent";
|
||||
import { NotebookConfigurationUtils } from "../../Utils/NotebookConfigurationUtils";
|
||||
import { NotebookContentItem } from "../Notebook/NotebookContentItem";
|
||||
|
||||
interface Kernel {
|
||||
name: string;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
export default class NotebookTab extends TabsBase implements ViewModels.Tab {
|
||||
private notebookAppIFrameSrc: ko.Computed<string>;
|
||||
private container: ViewModels.Explorer;
|
||||
public notebookPath: ko.Observable<string>;
|
||||
private messageListener: (ev: MessageEvent) => any;
|
||||
private activeCellTypeStr: string;
|
||||
private notebookContainerId: string;
|
||||
private currentKernelName: string;
|
||||
private availableKernels: Kernel[];
|
||||
private messageHandler: NotebookAppMessageHandler;
|
||||
private notificationProgressId: string;
|
||||
private isSwitchingKernels: ko.Observable<boolean>;
|
||||
|
||||
constructor(options: ViewModels.NotebookTabOptions) {
|
||||
super(options);
|
||||
this.availableKernels = [];
|
||||
this.isSwitchingKernels = ko.observable<boolean>(false);
|
||||
this.messageListener = async (ev: MessageEvent) => {
|
||||
if (isInvalidParentFrameOrigin(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const msg: NotebookAppContracts.FromNotebookMessage = ev.data;
|
||||
|
||||
if (msg.actionType === NotebookAppContracts.ActionTypes.Response) {
|
||||
this.messageHandler.handleCachedDataMessage(msg);
|
||||
} else if (msg.actionType === NotebookAppContracts.ActionTypes.Update) {
|
||||
const updateMessage = msg.message as NotebookAppContracts.FromNotebookUpdateMessage;
|
||||
switch (updateMessage.type) {
|
||||
case NotebookAppContracts.NotebookUpdateTypes.ActiveCellType:
|
||||
this.activeCellTypeStr = updateMessage.arg;
|
||||
this.updateNavbarWithTabsButtons();
|
||||
break;
|
||||
case NotebookAppContracts.NotebookUpdateTypes.KernelChange:
|
||||
this.isSwitchingKernels(false);
|
||||
this.currentKernelName = updateMessage.arg;
|
||||
this.messageHandler
|
||||
.sendCachedDataMessage<NotebookAppContracts.KernelSpecs>(NotebookAppContracts.MessageTypes.KernelList)
|
||||
.then(specs => {
|
||||
this.availableKernels = Object.keys(specs.kernelSpecs)
|
||||
.map((name: string) => ({ name: name, displayName: specs.kernelSpecs[name].displayName }))
|
||||
.sort((a: NotebookAppContracts.KernelOption, b: NotebookAppContracts.KernelOption) => {
|
||||
// Put default at the top, otherwise lexicographically compare
|
||||
if (a.name === specs.defaultName) {
|
||||
return -1;
|
||||
} else if (b.name === specs.defaultName) {
|
||||
return 1;
|
||||
} else {
|
||||
return a.displayName.localeCompare(b.displayName);
|
||||
}
|
||||
});
|
||||
this.updateNavbarWithTabsButtons();
|
||||
});
|
||||
|
||||
this.updateNavbarWithTabsButtons();
|
||||
await this.configureServiceEndpoints(this.currentKernelName);
|
||||
break;
|
||||
case NotebookAppContracts.NotebookUpdateTypes.ClickEvent:
|
||||
this.simulateClick();
|
||||
break;
|
||||
case NotebookAppContracts.NotebookUpdateTypes.SessionStatusChange: {
|
||||
this.handleSessionStateChange(updateMessage.arg as NotebookAppContracts.KernelStatusStates);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.error("Unknown command", updateMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
super.onTemplateReady((isTemplateReady: boolean) => {
|
||||
if (isTemplateReady) {
|
||||
window.addEventListener("message", this.messageListener, false);
|
||||
|
||||
const iFrame: HTMLIFrameElement = document.getElementById(this.notebookContainerId) as HTMLIFrameElement;
|
||||
this.messageHandler = new NotebookAppMessageHandler(iFrame.contentWindow);
|
||||
|
||||
this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.Status);
|
||||
}
|
||||
});
|
||||
this.container = options.container;
|
||||
|
||||
this.notebookAppIFrameSrc = ko.computed<string>(() =>
|
||||
NotebookTerminalComponent.createNotebookAppSrc(
|
||||
this.container.notebookServerInfo(),
|
||||
new Map<string, string>([["notebookpath", options.notebookContentItem.path]])
|
||||
)
|
||||
);
|
||||
this.notebookPath = ko.observable(options.notebookContentItem.path);
|
||||
|
||||
this.notebookContainerId = `notebookContainer-${this.tabId}`;
|
||||
|
||||
this.container.notebookServerInfo.subscribe((newValue: DataModels.NotebookWorkspaceConnectionInfo) => {
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, "New notebook server info received.");
|
||||
});
|
||||
|
||||
this.container &&
|
||||
this.container.arcadiaToken.subscribe(async () => {
|
||||
const currentKernel = this.currentKernelName;
|
||||
if (!currentKernel) {
|
||||
return;
|
||||
}
|
||||
await this.configureServiceEndpoints(currentKernel);
|
||||
});
|
||||
}
|
||||
|
||||
public onCloseTabButtonClick(): Q.Promise<any> {
|
||||
const cleanup = () => {
|
||||
this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.Shutdown);
|
||||
window.removeEventListener("message", this.messageListener);
|
||||
this.isActive(false);
|
||||
super.onCloseTabButtonClick();
|
||||
};
|
||||
|
||||
return this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.IsDirty).then((isDirty: boolean) => {
|
||||
if (isDirty) {
|
||||
this.container.showOkCancelModalDialog(
|
||||
"Close without saving?",
|
||||
`File has unsaved changes, close without saving?`,
|
||||
"Close",
|
||||
cleanup,
|
||||
"Cancel",
|
||||
undefined
|
||||
);
|
||||
return Q.resolve(null);
|
||||
} else {
|
||||
cleanup();
|
||||
return Q.resolve(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public onActivate(): Q.Promise<any> {
|
||||
if (this.messageHandler) {
|
||||
this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.Status);
|
||||
}
|
||||
return super.onActivate();
|
||||
}
|
||||
|
||||
public async reconfigureServiceEndpoints() {
|
||||
return await this.configureServiceEndpoints(this.currentKernelName);
|
||||
}
|
||||
|
||||
private handleSessionStateChange(state: NotebookAppContracts.KernelStatusStates) {
|
||||
switch (state) {
|
||||
case "reconnecting":
|
||||
this.clearProgressNotification();
|
||||
this.notificationProgressId = NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.InProgress,
|
||||
"Connection with Notebook Server lost. Reconnecting..."
|
||||
);
|
||||
break;
|
||||
case "dead":
|
||||
// This happens when the jupyter server detects that the kernel to which the cell was connected is no longer alive.
|
||||
// It can be caused by the jupyter server going down and back up again and informing the client that the kernel to which
|
||||
// it was previously connected to doesn't exist anymore. Send a restart kernel command.
|
||||
if (!this.isSwitchingKernels()) {
|
||||
this.notificationProgressId = NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.InProgress,
|
||||
"Connection with Notebook Server dead. Trying to reconnect..."
|
||||
);
|
||||
this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.RestartKernel);
|
||||
}
|
||||
break;
|
||||
case "connected":
|
||||
this.clearProgressNotification();
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Info,
|
||||
"Connection with Notebook Server established."
|
||||
);
|
||||
break;
|
||||
default:
|
||||
this.clearProgressNotification();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private clearProgressNotification() {
|
||||
if (this.notificationProgressId) {
|
||||
NotificationConsoleUtils.clearInProgressMessageWithId(this.notificationProgressId);
|
||||
this.notificationProgressId = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private static isUntitledNotebook(notebookFile: NotebookContentItem): boolean {
|
||||
return notebookFile.name.indexOf("Untitled") === 0;
|
||||
}
|
||||
|
||||
protected getContainer(): ViewModels.Explorer {
|
||||
return this.container;
|
||||
}
|
||||
|
||||
protected getTabsButtons(): ViewModels.NavbarButtonConfig[] {
|
||||
const saveLabel = "Save";
|
||||
const workspaceLabel = "Workspace";
|
||||
const kernelLabel = "Kernel";
|
||||
const runLabel = "Run";
|
||||
const runActiveCellLabel = "Run Active Cell";
|
||||
const runAllLabel = "Run All";
|
||||
const restartKernelLabel = "Restart Kernel";
|
||||
const clearLabel = "Clear outputs";
|
||||
const newCellLabel = "New Cell";
|
||||
const cellTypeLabel = "Cell Type";
|
||||
const codeLabel = "Code";
|
||||
const markdownLabel = "Markdown";
|
||||
const rawLabel = "Raw";
|
||||
const copyLabel = "Copy";
|
||||
const cutLabel = "Cut";
|
||||
const pasteLabel = "Paste";
|
||||
const undoLabel = "Undo";
|
||||
const redoLabel = "Redo";
|
||||
const cellCodeType = "code";
|
||||
const cellMarkdownType = "markdown";
|
||||
const cellRawType = "raw";
|
||||
let buttons: ViewModels.NavbarButtonConfig[] = [
|
||||
{
|
||||
iconSrc: SaveIcon,
|
||||
iconAlt: saveLabel,
|
||||
onCommandClick: () =>
|
||||
this.sendMessageToNotebook(
|
||||
NotebookAppContracts.MessageTypes.Save
|
||||
).then((result: NotebookAppContracts.ContentItem) =>
|
||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `File "${result.name}" was saved.`)
|
||||
),
|
||||
commandButtonLabel: saveLabel,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
ariaLabel: saveLabel
|
||||
},
|
||||
{
|
||||
iconSrc: null,
|
||||
iconAlt: kernelLabel,
|
||||
onCommandClick: () => {},
|
||||
commandButtonLabel: null,
|
||||
hasPopup: false,
|
||||
disabled: this.availableKernels.length < 1,
|
||||
isDropdown: true,
|
||||
dropdownPlaceholder: kernelLabel,
|
||||
dropdownSelectedKey: this.currentKernelName,
|
||||
dropdownWidth: 100,
|
||||
children: this.availableKernels.map((kernel: { name: string; displayName: string }) => ({
|
||||
iconSrc: null,
|
||||
iconAlt: kernel.displayName,
|
||||
onCommandClick: () => {
|
||||
this.isSwitchingKernels(true);
|
||||
this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.ChangeKernel, kernel.name);
|
||||
},
|
||||
commandButtonLabel: kernel.displayName,
|
||||
dropdownItemKey: kernel.name,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
ariaLabel: kernel.displayName
|
||||
})),
|
||||
ariaLabel: kernelLabel
|
||||
},
|
||||
{
|
||||
iconSrc: RunIcon,
|
||||
iconAlt: runLabel,
|
||||
onCommandClick: () => this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.RunAndAdvance),
|
||||
commandButtonLabel: runLabel,
|
||||
ariaLabel: runLabel,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
children: [
|
||||
{
|
||||
iconSrc: RunIcon,
|
||||
iconAlt: runActiveCellLabel,
|
||||
onCommandClick: () => this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.RunAndAdvance),
|
||||
commandButtonLabel: runActiveCellLabel,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
ariaLabel: runActiveCellLabel
|
||||
},
|
||||
{
|
||||
iconSrc: RunAllIcon,
|
||||
iconAlt: runAllLabel,
|
||||
onCommandClick: () => this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.RunAll),
|
||||
commandButtonLabel: runAllLabel,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
ariaLabel: runAllLabel
|
||||
},
|
||||
// {
|
||||
// iconSrc: null,
|
||||
// onCommandClick: () => this.postMessage("switchKernel"),
|
||||
// commandButtonLabel: "Switch Kernel",
|
||||
// hasPopup: false,
|
||||
// disabled: false
|
||||
// },
|
||||
{
|
||||
iconSrc: RestartIcon,
|
||||
iconAlt: restartKernelLabel,
|
||||
onCommandClick: () =>
|
||||
this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.RestartKernel).then(
|
||||
(isSuccessful: boolean) => {
|
||||
// Note: don't handle isSuccessful === false as it gets triggered if user cancels kernel restart modal dialog
|
||||
if (isSuccessful) {
|
||||
NotificationConsoleUtils.logConsoleMessage(
|
||||
ConsoleDataType.Info,
|
||||
"Kernel was successfully restarted"
|
||||
);
|
||||
}
|
||||
}
|
||||
),
|
||||
commandButtonLabel: restartKernelLabel,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
ariaLabel: restartKernelLabel
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
iconSrc: ClearAllOutputsIcon,
|
||||
iconAlt: clearLabel,
|
||||
onCommandClick: () => this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.ClearAllOutputs),
|
||||
commandButtonLabel: clearLabel,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
ariaLabel: clearLabel
|
||||
},
|
||||
{
|
||||
iconSrc: NewCellIcon,
|
||||
iconAlt: newCellLabel,
|
||||
onCommandClick: () => this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.InsertBelow),
|
||||
commandButtonLabel: newCellLabel,
|
||||
ariaLabel: newCellLabel,
|
||||
hasPopup: false,
|
||||
disabled: false
|
||||
},
|
||||
CommandBarComponentButtonFactory.createDivider(),
|
||||
{
|
||||
iconSrc: null,
|
||||
iconAlt: null,
|
||||
onCommandClick: () => {},
|
||||
commandButtonLabel: null,
|
||||
ariaLabel: cellTypeLabel,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
isDropdown: true,
|
||||
dropdownPlaceholder: cellTypeLabel,
|
||||
dropdownSelectedKey: this.activeCellTypeStr,
|
||||
dropdownWidth: 110,
|
||||
children: [
|
||||
{
|
||||
iconSrc: null,
|
||||
iconAlt: null,
|
||||
onCommandClick: () =>
|
||||
this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.ChangeCellType, cellCodeType),
|
||||
commandButtonLabel: codeLabel,
|
||||
ariaLabel: codeLabel,
|
||||
dropdownItemKey: cellCodeType,
|
||||
hasPopup: false,
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
iconSrc: null,
|
||||
iconAlt: null,
|
||||
onCommandClick: () =>
|
||||
this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.ChangeCellType, cellMarkdownType),
|
||||
commandButtonLabel: markdownLabel,
|
||||
ariaLabel: markdownLabel,
|
||||
dropdownItemKey: cellMarkdownType,
|
||||
hasPopup: false,
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
iconSrc: null,
|
||||
iconAlt: null,
|
||||
onCommandClick: () =>
|
||||
this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.ChangeCellType, cellRawType),
|
||||
commandButtonLabel: rawLabel,
|
||||
ariaLabel: rawLabel,
|
||||
dropdownItemKey: cellRawType,
|
||||
hasPopup: false,
|
||||
disabled: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
iconSrc: CopyIcon,
|
||||
iconAlt: copyLabel,
|
||||
onCommandClick: () => this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.Copy),
|
||||
commandButtonLabel: copyLabel,
|
||||
ariaLabel: copyLabel,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
children: [
|
||||
{
|
||||
iconSrc: CopyIcon,
|
||||
iconAlt: copyLabel,
|
||||
onCommandClick: () => this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.Copy),
|
||||
commandButtonLabel: copyLabel,
|
||||
ariaLabel: copyLabel,
|
||||
hasPopup: false,
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
iconSrc: CutIcon,
|
||||
iconAlt: cutLabel,
|
||||
onCommandClick: () => this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.Cut),
|
||||
commandButtonLabel: cutLabel,
|
||||
ariaLabel: cutLabel,
|
||||
hasPopup: false,
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
iconSrc: PasteIcon,
|
||||
iconAlt: pasteLabel,
|
||||
onCommandClick: () => this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.Paste),
|
||||
commandButtonLabel: pasteLabel,
|
||||
ariaLabel: pasteLabel,
|
||||
hasPopup: false,
|
||||
disabled: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
iconSrc: UndoIcon,
|
||||
iconAlt: undoLabel,
|
||||
onCommandClick: () => this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.Undo),
|
||||
commandButtonLabel: undoLabel,
|
||||
ariaLabel: undoLabel,
|
||||
hasPopup: false,
|
||||
disabled: false,
|
||||
children: [
|
||||
{
|
||||
iconSrc: UndoIcon,
|
||||
iconAlt: undoLabel,
|
||||
onCommandClick: () => this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.Undo),
|
||||
commandButtonLabel: undoLabel,
|
||||
ariaLabel: undoLabel,
|
||||
hasPopup: false,
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
iconSrc: RedoIcon,
|
||||
iconAlt: redoLabel,
|
||||
onCommandClick: () => this.sendMessageToNotebook(NotebookAppContracts.MessageTypes.Redo),
|
||||
commandButtonLabel: redoLabel,
|
||||
ariaLabel: redoLabel,
|
||||
hasPopup: false,
|
||||
disabled: false
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
if (this.container.hasStorageAnalyticsAfecFeature()) {
|
||||
const arcadiaWorkspaceDropdown: ViewModels.NavbarButtonConfig = {
|
||||
iconSrc: null,
|
||||
iconAlt: workspaceLabel,
|
||||
ariaLabel: workspaceLabel,
|
||||
onCommandClick: () => {},
|
||||
commandButtonLabel: null,
|
||||
hasPopup: false,
|
||||
disabled: this.container.arcadiaWorkspaces.length < 1,
|
||||
isDropdown: false,
|
||||
isArcadiaPicker: true,
|
||||
arcadiaProps: {
|
||||
selectedSparkPool: null,
|
||||
workspaces: this.container.arcadiaWorkspaces(),
|
||||
onSparkPoolSelect: () => {},
|
||||
onCreateNewWorkspaceClicked: () => {
|
||||
this.container.createWorkspace();
|
||||
},
|
||||
onCreateNewSparkPoolClicked: (workspaceResourceId: string) => {
|
||||
this.container.createSparkPool(workspaceResourceId);
|
||||
}
|
||||
}
|
||||
};
|
||||
buttons.splice(1, 0, arcadiaWorkspaceDropdown);
|
||||
}
|
||||
return buttons;
|
||||
}
|
||||
|
||||
protected buildCommandBarOptions(): void {
|
||||
this.updateNavbarWithTabsButtons();
|
||||
}
|
||||
|
||||
private async configureServiceEndpoints(kernelName: string) {
|
||||
const notebookConnectionInfo = this.container && this.container.notebookServerInfo();
|
||||
const sparkClusterConnectionInfo = this.container && this.container.sparkClusterConnectionInfo();
|
||||
await NotebookConfigurationUtils.configureServiceEndpoints(
|
||||
this.notebookPath(),
|
||||
notebookConnectionInfo,
|
||||
kernelName,
|
||||
sparkClusterConnectionInfo
|
||||
);
|
||||
}
|
||||
|
||||
private sendMessageToNotebook(type: NotebookAppContracts.MessageTypes, arg?: string): Q.Promise<any> {
|
||||
return this.messageHandler.sendCachedDataMessage(type, arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* The iframe swallows any click event which breaks the logic to dismiss the menu, so we simulate a click from the message
|
||||
*/
|
||||
private simulateClick() {
|
||||
if (!this.tabId) {
|
||||
return;
|
||||
}
|
||||
const event = document.createEvent("Events");
|
||||
event.initEvent("click", true, false);
|
||||
document.getElementById(this.tabId).dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,6 @@ import Explorer from "../Explorer";
|
||||
import { CollectionStub, DatabaseStub } from "../../Explorer/OpenActionsStubs";
|
||||
import QueryTab from "./QueryTab";
|
||||
|
||||
jest.mock("./NotebookTab");
|
||||
|
||||
describe("Query Tab", () => {
|
||||
function getNewQueryTabForContainer(container: ViewModels.Explorer): ViewModels.QueryTab {
|
||||
const database: ViewModels.Database = new DatabaseStub({
|
||||
|
||||
@@ -9,8 +9,6 @@ import Explorer from "../Explorer";
|
||||
import SettingsTab from "../Tabs/SettingsTab";
|
||||
import { DataAccessUtility } from "../../Platform/Portal/DataAccessUtility";
|
||||
|
||||
jest.mock("./NotebookTab");
|
||||
|
||||
describe("Settings tab", () => {
|
||||
const baseCollection: DataModels.Collection = {
|
||||
defaultTtl: 200,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import DocumentsTabTemplate from "./DocumentsTab.html";
|
||||
import ConflictsTabTemplate from "./ConflictsTab.html";
|
||||
import GraphTabTemplate from "./GraphTab.html";
|
||||
import NotebookTabTemplate from "./NotebookTab.html";
|
||||
import SparkMasterTabTemplate from "./SparkMasterTab.html";
|
||||
import NotebookV2TabTemplate from "./NotebookV2Tab.html";
|
||||
import TerminalTabTemplate from "./TerminalTab.html";
|
||||
@@ -51,15 +50,6 @@ export class GraphTab {
|
||||
}
|
||||
}
|
||||
|
||||
export class NotebookTab {
|
||||
constructor() {
|
||||
return {
|
||||
viewModel: TabComponent,
|
||||
template: NotebookTabTemplate
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class SparkMasterTab {
|
||||
constructor() {
|
||||
return {
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import TabsBase from "../TabsBase";
|
||||
|
||||
export default class NotebookTab extends TabsBase implements ViewModels.Tab {
|
||||
constructor(options: ViewModels.NotebookTabOptions) {
|
||||
super(options);
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,6 @@ import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
|
||||
import { OfferUtils } from "../../Utils/OfferUtils";
|
||||
import { StartUploadMessageParams, UploadDetails, UploadDetailsRecord } from "../../workers/upload/definitions";
|
||||
import { ContextMenuButtonFactory } from "../ContextMenuButtonFactory";
|
||||
import ContextMenu from "../Menus/ContextMenu";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { CassandraAPIDataClient, CassandraTableKey, CassandraTableKeys } from "../Tables/TableDataClient";
|
||||
import { ConflictsTab } from "../Tabs/ConflictsTab";
|
||||
@@ -32,6 +30,7 @@ import DocumentId from "./DocumentId";
|
||||
import StoredProcedure from "./StoredProcedure";
|
||||
import Trigger from "./Trigger";
|
||||
import UserDefinedFunction from "./UserDefinedFunction";
|
||||
import { config } from "../../Config";
|
||||
|
||||
export default class Collection implements ViewModels.Collection {
|
||||
public nodeKind: string;
|
||||
@@ -85,9 +84,6 @@ export default class Collection implements ViewModels.Collection {
|
||||
public userDefinedFunctionsFocused: ko.Observable<boolean>;
|
||||
public triggersFocused: ko.Observable<boolean>;
|
||||
|
||||
public contextMenu: ViewModels.ContextMenu;
|
||||
public documentsContextMenu: ViewModels.ContextMenu;
|
||||
|
||||
constructor(
|
||||
container: ViewModels.Explorer,
|
||||
databaseId: string,
|
||||
@@ -215,217 +211,8 @@ export default class Collection implements ViewModels.Collection {
|
||||
this.isStoredProceduresExpanded = ko.observable<boolean>(false);
|
||||
this.isUserDefinedFunctionsExpanded = ko.observable<boolean>(false);
|
||||
this.isTriggersExpanded = ko.observable<boolean>(false);
|
||||
this.contextMenu = new ContextMenu(this.container, this.rid);
|
||||
this.contextMenu.options(
|
||||
ContextMenuButtonFactory.createCollectionContextMenuButton(container, {
|
||||
databaseId: this.databaseId,
|
||||
collectionId: this.id()
|
||||
})
|
||||
);
|
||||
this.documentsContextMenu = new ContextMenu(this.container, `${this.rid}/documents`);
|
||||
}
|
||||
|
||||
public onKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
|
||||
this.expandCollapseCollection();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === "Delete") {
|
||||
this.onDeleteCollectionContextMenuClick(source, event);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowRight" && !this.isCollectionExpanded()) {
|
||||
this.expandCollection();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowDown" && !this.isCollectionExpanded()) {
|
||||
this.expandCollection();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowLeft" && this.isCollectionExpanded()) {
|
||||
this.collapseCollection();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onMenuKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === "Escape") {
|
||||
this.contextMenu.hide(source, event);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onTableEntitiesKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
return true;
|
||||
};
|
||||
|
||||
public onTableEntitiesKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
|
||||
this.onTableEntitiesClick();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onGraphDocumentsKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
return true;
|
||||
};
|
||||
|
||||
public onGraphDocumentsKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
|
||||
this.onGraphDocumentsClick();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onDocumentDBDocumentsKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
return true;
|
||||
};
|
||||
|
||||
public onDocumentDBDocumentsKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
|
||||
this.onDocumentDBDocumentsClick();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onConflictsKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
|
||||
this.onConflictsClick();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onMongoDBDocumentsKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
return true;
|
||||
};
|
||||
|
||||
public onMongoDBDocumentsKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
|
||||
this.onMongoDBDocumentsClick();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onSettingsKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
return true;
|
||||
};
|
||||
|
||||
public onSettingsKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
|
||||
this.onSettingsClick();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onStoredProceduresKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === "ArrowRight" && !this.isStoredProceduresExpanded()) {
|
||||
this.expandStoredProcedures();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowDown" && !this.isStoredProceduresExpanded()) {
|
||||
this.expandStoredProcedures();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowLeft" && this.isStoredProceduresExpanded()) {
|
||||
this.collapseStoredProcedures();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onStoredProceduresKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
|
||||
this.expandCollapseStoredProcedures();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onUserDefinedFunctionsKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === "ArrowRight" && !this.isUserDefinedFunctionsExpanded()) {
|
||||
this.expandUserDefinedFunctions();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowDown" && !this.isUserDefinedFunctionsExpanded()) {
|
||||
this.expandUserDefinedFunctions();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowLeft" && this.isUserDefinedFunctionsExpanded()) {
|
||||
this.collapseUserDefinedFunctions();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onUserDefinedFunctionsKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
|
||||
this.expandCollapseUserDefinedFunctions();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onTriggersKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === "ArrowRight" && !this.isTriggersExpanded()) {
|
||||
this.expandTriggers();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowDown" && !this.isTriggersExpanded()) {
|
||||
this.expandTriggers();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowLeft" && this.isTriggersExpanded()) {
|
||||
this.collapseTriggers();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onTriggersKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
|
||||
this.expandCollapseTriggers();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
public expandCollapseCollection() {
|
||||
this.container.selectedNode(this);
|
||||
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
|
||||
@@ -984,9 +771,6 @@ export default class Collection implements ViewModels.Collection {
|
||||
|
||||
// Activate
|
||||
queryTab.onTabClick();
|
||||
|
||||
// Hide Context Menu (if necessary)
|
||||
collection.contextMenu.hide(this, null);
|
||||
}
|
||||
|
||||
public onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string) {
|
||||
@@ -1024,9 +808,6 @@ export default class Collection implements ViewModels.Collection {
|
||||
|
||||
// Activate
|
||||
queryTab.onTabClick();
|
||||
|
||||
// Hide Context Menu (if necessary)
|
||||
collection.contextMenu.hide(this, null);
|
||||
}
|
||||
|
||||
public onNewGraphClick() {
|
||||
@@ -1035,8 +816,6 @@ export default class Collection implements ViewModels.Collection {
|
||||
this.container.openedTabs.push(graphTab);
|
||||
// Activate
|
||||
graphTab.onTabClick();
|
||||
// Hide Context Menu (if necessary)
|
||||
this.contextMenu.hide(this, null);
|
||||
}
|
||||
|
||||
public onNewMongoShellClick() {
|
||||
@@ -1059,9 +838,6 @@ export default class Collection implements ViewModels.Collection {
|
||||
|
||||
// Activate
|
||||
mongoShellTab.onTabClick();
|
||||
|
||||
// Hide Context Menu (if necessary)
|
||||
this.contextMenu.hide(this, null);
|
||||
}
|
||||
|
||||
public onNewStoredProcedureClick(source: ViewModels.Collection, event: MouseEvent) {
|
||||
@@ -1356,7 +1132,6 @@ export default class Collection implements ViewModels.Collection {
|
||||
}
|
||||
|
||||
public onDeleteCollectionContextMenuClick(source: ViewModels.Collection, event: MouseEvent | KeyboardEvent) {
|
||||
this._onContextMenuClick(source, event);
|
||||
this.container.deleteCollectionConfirmationPane.open();
|
||||
}
|
||||
|
||||
@@ -1416,7 +1191,7 @@ export default class Collection implements ViewModels.Collection {
|
||||
masterKey: CosmosClient.masterKey(),
|
||||
endpoint: CosmosClient.endpoint(),
|
||||
accessToken: CosmosClient.accessToken(),
|
||||
platform: window.dataExplorerPlatform,
|
||||
platform: config.platform,
|
||||
databaseAccount: CosmosClient.databaseAccount()
|
||||
}
|
||||
};
|
||||
@@ -1604,11 +1379,6 @@ export default class Collection implements ViewModels.Collection {
|
||||
});
|
||||
}
|
||||
|
||||
private _onContextMenuClick(source: ViewModels.Collection, event: MouseEvent | KeyboardEvent) {
|
||||
this.container.selectedNode(this);
|
||||
this.contextMenu.hide(source, event);
|
||||
}
|
||||
|
||||
protected _getOfferForCollection(offers: DataModels.Offer[], collection: DataModels.Collection): DataModels.Offer {
|
||||
return _.find(offers, (offer: DataModels.Offer) => offer.resource.indexOf(collection._rid) >= 0);
|
||||
}
|
||||
|
||||
@@ -1,425 +0,0 @@
|
||||
<div class="pointerCursor">
|
||||
<div
|
||||
role="treeitem"
|
||||
data-test="collectionList"
|
||||
tabindex="0"
|
||||
class="collectionMenu treeHovermargin highlight"
|
||||
data-bind="
|
||||
click: $data.expandCollapseCollection,
|
||||
clickBubble: false,
|
||||
contextmenuBubble: false,
|
||||
css:{
|
||||
collectionNodeSelected: isCollectionNodeSelected(),
|
||||
contextmenushowing: $data.contextMenu.visible
|
||||
},
|
||||
event: {
|
||||
keydown: onKeyDown,
|
||||
keypress: onKeyPress,
|
||||
contextmenu: $data.contextMenu.show,
|
||||
drop: $data.onDrop,
|
||||
dragover: $data.onDragOver
|
||||
},
|
||||
attr:{
|
||||
'aria-expanded': $data.isCollectionExpanded,
|
||||
'aria-selected': isCollectionNodeSelected()
|
||||
}"
|
||||
>
|
||||
<span
|
||||
class="collectionList databaseCollChildTextOverflow"
|
||||
data-bind="
|
||||
attr: {
|
||||
title: $data.id()
|
||||
}"
|
||||
>
|
||||
<img
|
||||
class="imgiconwidth collectionsTreeCollapseExpand"
|
||||
src="/Triangle-right.svg"
|
||||
alt="Show collection properties"
|
||||
data-bind="visible: !$data.isCollectionExpanded()"
|
||||
/>
|
||||
<img
|
||||
class="imgiconwidth collectionsTreeCollapseExpand"
|
||||
src="/Triangle-down.svg"
|
||||
alt="Hide collection properties"
|
||||
data-bind="visible: $data.isCollectionExpanded()"
|
||||
/>
|
||||
<img
|
||||
src="/tree-collection.svg"
|
||||
data-bind="
|
||||
attr: {
|
||||
alt: container.addCollectionText
|
||||
}"
|
||||
/>
|
||||
<!--ko text: $data.id-->
|
||||
<!--/ko-->
|
||||
</span>
|
||||
<span
|
||||
class="menuEllipsis"
|
||||
data-test="collectionEllipsisMenu"
|
||||
name="context menu"
|
||||
role="button"
|
||||
data-bind="click: $data.contextMenu.show, clickBubble: false"
|
||||
>…</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- Collection node children - Start -->
|
||||
<div
|
||||
class="collectionChildList"
|
||||
role="group"
|
||||
data-bind="
|
||||
visible: $data.isCollectionExpanded,
|
||||
clickBubble: false"
|
||||
>
|
||||
<!-- Documents Node - Start -->
|
||||
<div>
|
||||
<div
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
class="documentsMenu"
|
||||
data-bind="
|
||||
visible: $root.isPreferredApiDocumentDB(),
|
||||
click: $data.onDocumentDBDocumentsClick,
|
||||
event: {
|
||||
keydown: onDocumentDBDocumentsKeyDown,
|
||||
keypress: onDocumentDBDocumentsKeyPress
|
||||
},
|
||||
clickBubble: false,
|
||||
css: {
|
||||
highlight: true,
|
||||
collectionNodeSelected: isSubNodeSelected(0)
|
||||
},
|
||||
attr:{
|
||||
'aria-selected': isSubNodeSelected(0)
|
||||
}"
|
||||
>
|
||||
<span class="databaseDocuments">Items</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Documents Node - End -->
|
||||
|
||||
<!-- Entitites Node - Start -->
|
||||
<div>
|
||||
<div
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
class="documentsMenu"
|
||||
data-bind="
|
||||
visible: $root.isPreferredApiTable(),
|
||||
click: $data.onTableEntitiesClick,
|
||||
event: {
|
||||
keydown: onTableEntitiesKeyDown,
|
||||
keypress: onTableEntitiesKeyPress
|
||||
},
|
||||
clickBubble: false,
|
||||
css: {
|
||||
highlight: true,
|
||||
collectionNodeSelected: $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid && $data.selectedSubnodeKind() === 9
|
||||
},
|
||||
attr:{
|
||||
'aria-selected': $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid && $data.selectedSubnodeKind() === 9
|
||||
}"
|
||||
>
|
||||
<span class="databaseDocuments">Entities</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Entitites Node - End -->
|
||||
|
||||
<!-- Rows Node - Start -->
|
||||
<div>
|
||||
<div
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
class="documentsMenu"
|
||||
data-bind="
|
||||
visible: $root.isPreferredApiCassandra(),
|
||||
click: $data.onTableEntitiesClick,
|
||||
event: {
|
||||
keydown: onTableEntitiesKeyDown,
|
||||
keypress: onTableEntitiesKeyPress
|
||||
},
|
||||
clickBubble: false,
|
||||
css: {
|
||||
highlight: true,
|
||||
collectionNodeSelected: $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid && $data.selectedSubnodeKind() === 9
|
||||
},
|
||||
attr:{
|
||||
'aria-selected': $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid && $data.selectedSubnodeKind() === 9
|
||||
}"
|
||||
>
|
||||
<span class="databaseDocuments">Rows</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Rows Node - End -->
|
||||
|
||||
<!-- Graph Node - Start -->
|
||||
<div>
|
||||
<div
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
class="documentsMenu"
|
||||
data-bind="
|
||||
visible: $root.isPreferredApiGraph,
|
||||
click: $data.onGraphDocumentsClick,
|
||||
event: {
|
||||
keydown: onGraphDocumentsKeyDown,
|
||||
keypress: onGraphDocumentsKeyPress
|
||||
},
|
||||
clickBubble: false,
|
||||
css: {
|
||||
highlight: true,
|
||||
collectionNodeSelected: $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid && $data.selectedSubnodeKind() === 6
|
||||
},
|
||||
attr:{
|
||||
'aria-selected': $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid && $data.selectedSubnodeKind() === 6
|
||||
}"
|
||||
>
|
||||
<span class="databaseDocuments">Graph</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Graph Node - End -->
|
||||
|
||||
<!-- MongoDB Documents Node - Start -->
|
||||
<div>
|
||||
<div
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
class="documentsMenu"
|
||||
data-bind="
|
||||
visible: $root.isPreferredApiMongoDB,
|
||||
click: $data.onMongoDBDocumentsClick,
|
||||
event: {
|
||||
keydown: onMongoDBDocumentsKeyDown,
|
||||
keypress: onMongoDBDocumentsKeyPress
|
||||
},
|
||||
clickBubble: false,
|
||||
css: {
|
||||
highlight: true,
|
||||
collectionNodeSelected: $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid && $data.selectedSubnodeKind() === 0
|
||||
},
|
||||
attr:{
|
||||
'aria-selected': $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid && $data.selectedSubnodeKind() === 0
|
||||
}"
|
||||
>
|
||||
<span class="databaseDocuments">Documents</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- MongoDB Documents Node - End -->
|
||||
|
||||
<!-- Scale & Setings Node - Start -->
|
||||
<div>
|
||||
<div
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
data-bind="
|
||||
click: $data.onSettingsClick,
|
||||
event: {
|
||||
keydown: onSettingsKeyDown,
|
||||
keypress: onSettingsKeyPress
|
||||
},
|
||||
css: {
|
||||
highlight: true,
|
||||
collectionNodeSelected: $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid && $data.selectedSubnodeKind() === 1
|
||||
},
|
||||
attr:{
|
||||
'aria-selected': $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid && $data.selectedSubnodeKind() === 1
|
||||
}"
|
||||
>
|
||||
<span class="databaseDocuments">
|
||||
<!-- ko if: !$data.database.isDatabaseShared() -->
|
||||
Scale & Settings
|
||||
<!-- /ko -->
|
||||
<!-- ko if: $data.database.isDatabaseShared() -->
|
||||
Settings
|
||||
<!-- /ko -->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Scale & Setings Node - End -->
|
||||
|
||||
<!-- Stored Procedures Node - Start -->
|
||||
<div>
|
||||
<div
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
class="storedProcedureMenu highlight"
|
||||
data-bind="
|
||||
click: $data.expandCollapseStoredProcedures,
|
||||
event: {
|
||||
keydown: onStoredProceduresKeyDown,
|
||||
keypress: onStoredProceduresKeyPress
|
||||
},
|
||||
css: {
|
||||
collectionNodeSelected: !isStoredProceduresExpanded() && isSubNodeSelected(2)
|
||||
},
|
||||
attr:{
|
||||
'aria-expanded': $data.isStoredProceduresExpanded(),
|
||||
'aria-selected': !isStoredProceduresExpanded() && isSubNodeSelected(2)
|
||||
},
|
||||
visible: showStoredProcedures"
|
||||
>
|
||||
<span
|
||||
class="collectionMenuChildren"
|
||||
data-bind="
|
||||
attr: {
|
||||
title: $data.id()
|
||||
}"
|
||||
>
|
||||
<img
|
||||
class="imgiconwidth collectionsTreeCollapseExpand"
|
||||
src="/Triangle-right.svg"
|
||||
alt="Show storedprocedures properties"
|
||||
data-bind="visible: !$data.isStoredProceduresExpanded()"
|
||||
/>
|
||||
<img
|
||||
class="imgiconwidth collectionsTreeCollapseExpand"
|
||||
src="/Triangle-down.svg"
|
||||
alt="Hide storedprocedures properties"
|
||||
data-bind="visible: $data.isStoredProceduresExpanded()"
|
||||
/>
|
||||
Stored Procedures
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="storedUdfTriggerMenu"
|
||||
data-bind=" visible: $data.isStoredProceduresExpanded(), foreach: $data.storedProcedures"
|
||||
>
|
||||
<stored-procedure-node params="{data: $data}"></stored-procedure-node>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Stored Procedures Node - End -->
|
||||
|
||||
<!-- User Defined Functions Node - Start -->
|
||||
<div>
|
||||
<div
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
class="userDefinedMenu highlight"
|
||||
data-bind="
|
||||
click: $data.expandCollapseUserDefinedFunctions,
|
||||
event: {
|
||||
keydown: onUserDefinedFunctionsKeyDown,
|
||||
keypress: onUserDefinedFunctionsKeyPress
|
||||
},
|
||||
css: {
|
||||
collectionNodeSelected: !isUserDefinedFunctionsExpanded() && isSubNodeSelected(3)
|
||||
},
|
||||
attr:{
|
||||
'aria-expanded': $data.isUserDefinedFunctionsExpanded(),
|
||||
'aria-selected': !isUserDefinedFunctionsExpanded() && isSubNodeSelected(3)
|
||||
},
|
||||
visible: showUserDefinedFunctions"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
class="collectionMenuChildren"
|
||||
data-bind="
|
||||
attr: {
|
||||
title: $data.id()
|
||||
}"
|
||||
>
|
||||
<img
|
||||
class="imgiconwidth collectionsTreeCollapseExpand"
|
||||
src="/Triangle-right.svg"
|
||||
alt="Show userdefinedfunctions properties"
|
||||
data-bind="visible: !$data.isUserDefinedFunctionsExpanded()"
|
||||
/>
|
||||
<img
|
||||
class="imgiconwidth collectionsTreeCollapseExpand"
|
||||
src="/Triangle-down.svg"
|
||||
alt="Hide userdefinedfunctions properties"
|
||||
data-bind="visible: $data.isUserDefinedFunctionsExpanded()"
|
||||
/>
|
||||
User Defined Functions
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="storedUdfTriggerMenu"
|
||||
data-bind="visible: $data.isUserDefinedFunctionsExpanded(), foreach: $data.userDefinedFunctions"
|
||||
>
|
||||
<user-defined-function-node params="{data: $data}"></user-defined-function-node>
|
||||
</div>
|
||||
</div>
|
||||
<!-- User Defined Functions Node - End -->
|
||||
|
||||
<!-- Triggers Node - Start -->
|
||||
<div>
|
||||
<div
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
class="triggersMenu highlight"
|
||||
data-bind="
|
||||
click: $data.expandCollapseTriggers,
|
||||
event: {
|
||||
keydown: onTriggersKeyDown,
|
||||
keypress: onTriggersKeyPress
|
||||
},
|
||||
css: {
|
||||
collectionNodeSelected: !isTriggersExpanded() && isSubNodeSelected(4)
|
||||
},
|
||||
attr:{
|
||||
'aria-expanded': $data.isTriggersExpanded(),
|
||||
'aria-selected': !isTriggersExpanded() && isSubNodeSelected(4)
|
||||
},
|
||||
visible: showTriggers"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
class="collectionMenuChildren"
|
||||
data-bind="
|
||||
attr: {
|
||||
title: $data.id()
|
||||
}"
|
||||
>
|
||||
<img
|
||||
class="imgiconwidth collectionsTreeCollapseExpand"
|
||||
src="/Triangle-right.svg"
|
||||
alt="Show Triggers properties"
|
||||
data-bind="visible: !$data.isTriggersExpanded()"
|
||||
/>
|
||||
<img
|
||||
class="imgiconwidth collectionsTreeCollapseExpand"
|
||||
src="/Triangle-down.svg"
|
||||
alt="Hide Triggers properties"
|
||||
data-bind="visible: $data.isTriggersExpanded()"
|
||||
/>
|
||||
Triggers
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="storedUdfTriggerMenu" data-bind="visible: $data.isTriggersExpanded(), foreach: $data.triggers">
|
||||
<trigger-node params="{data: $data}"></trigger-node>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Triggers Node - End -->
|
||||
|
||||
<!-- Conflicts Node - Start -->
|
||||
<div>
|
||||
<div
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
data-bind="
|
||||
click: $data.onConflictsClick,
|
||||
event: {
|
||||
keypress: onConflictsKeyPress
|
||||
},
|
||||
css: {
|
||||
highlight: true,
|
||||
collectionNodeSelected: isSubNodeSelected(12)
|
||||
},
|
||||
attr:{
|
||||
'aria-selected': isSubNodeSelected(12)
|
||||
},
|
||||
visible: showConflicts"
|
||||
>
|
||||
<span class=" databaseDocuments"> Conflicts </span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Conflicts Node - End -->
|
||||
</div>
|
||||
<!-- Collection node children - End -->
|
||||
</div>
|
||||
@@ -1,16 +0,0 @@
|
||||
<div data-bind="event: { keydown: onMenuKeyDown }">
|
||||
<div
|
||||
class="context-menu-background"
|
||||
data-bind="
|
||||
visible: $data.contextMenu.visible,
|
||||
click: $data.contextMenu.hide"
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="context-menu"
|
||||
data-test="collectionContextMenu"
|
||||
data-bind="attr:{ tabindex: $data.contextMenu.tabIndex, id: $data.contextMenu.elementId }, visible: $data.contextMenu.visible, foreach: $data.contextMenu.options"
|
||||
>
|
||||
<command-button class="context-menu-option" params="{buttonProps: $data}"></command-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -7,11 +7,9 @@ import * as DataModels from "../../Contracts/DataModels";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import DatabaseSettingsTab from "../Tabs/DatabaseSettingsTab";
|
||||
import Collection from "./Collection";
|
||||
import ContextMenu from "../Menus/ContextMenu";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
|
||||
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
|
||||
import { ContextMenuButtonFactory } from "../ContextMenuButtonFactory";
|
||||
import { Logger } from "../../Common/Logger";
|
||||
|
||||
export default class Database implements ViewModels.Database {
|
||||
@@ -25,7 +23,6 @@ export default class Database implements ViewModels.Database {
|
||||
public isDatabaseExpanded: ko.Observable<boolean>;
|
||||
public isDatabaseShared: ko.Computed<boolean>;
|
||||
public selectedSubnodeKind: ko.Observable<ViewModels.CollectionTabKind>;
|
||||
public contextMenu: ViewModels.ContextMenu;
|
||||
|
||||
constructor(container: ViewModels.Explorer, data: any, offer: DataModels.Offer) {
|
||||
this.nodeKind = "Database";
|
||||
@@ -36,71 +33,12 @@ export default class Database implements ViewModels.Database {
|
||||
this.offer = ko.observable(offer);
|
||||
this.collections = ko.observableArray<Collection>();
|
||||
this.isDatabaseExpanded = ko.observable<boolean>(false);
|
||||
this.contextMenu = new ContextMenu(this.container, this.rid);
|
||||
this.contextMenu.options(
|
||||
ContextMenuButtonFactory.createDatabaseContextMenuButton(container, { databaseId: this.id() })
|
||||
);
|
||||
this.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
|
||||
this.isDatabaseShared = ko.pureComputed(() => {
|
||||
return this.offer && !!this.offer();
|
||||
});
|
||||
}
|
||||
|
||||
public onKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === " " || event.key === "Enter") {
|
||||
this.expandCollapseDatabase();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === "Delete") {
|
||||
this.onDeleteDatabaseContextMenuClick(source, event);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowRight") {
|
||||
this.expandDatabase();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "ArrowLeft") {
|
||||
this.collapseDatabase();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === "Enter") {
|
||||
this.expandCollapseDatabase();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onMenuKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === "Escape") {
|
||||
this.contextMenu.hide(source, event);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onSettingsKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
return true;
|
||||
};
|
||||
|
||||
public onSettingsKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === " " || event.key === "Enter") {
|
||||
this.onSettingsClick();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onSettingsClick = () => {
|
||||
this.container.selectedNode(this);
|
||||
this.selectedSubnodeKind(ViewModels.CollectionTabKind.DatabaseSettings);
|
||||
@@ -262,8 +200,6 @@ export default class Database implements ViewModels.Database {
|
||||
}
|
||||
|
||||
public onDeleteDatabaseContextMenuClick(source: ViewModels.Database, event: MouseEvent | KeyboardEvent) {
|
||||
source.container.selectedNode(source);
|
||||
source.contextMenu.hide(source, event);
|
||||
this.container.deleteDatabaseConfirmationPane.open();
|
||||
}
|
||||
|
||||
@@ -346,7 +282,6 @@ export default class Database implements ViewModels.Database {
|
||||
}
|
||||
|
||||
public openAddCollection(database: Database, event: MouseEvent) {
|
||||
database.contextMenu.hide(database, event);
|
||||
database.container.addCollectionPane.databaseId(database.id());
|
||||
database.container.addCollectionPane.open();
|
||||
}
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
<div class="pointerCursor">
|
||||
<div
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
data-test="databaseMenu"
|
||||
class="databaseMenu treeHovermargin highlight"
|
||||
data-bind="
|
||||
click: $data.expandCollapseDatabase,
|
||||
event: {
|
||||
keydown: onKeyDown,
|
||||
keypress: onKeyPress,
|
||||
contextmenu: $data.contextMenu.show
|
||||
},
|
||||
clickBubble: false,
|
||||
contextmenuBubble: false,
|
||||
css:{
|
||||
contextmenushowing: $data.contextMenu.visible,
|
||||
highlight: true,
|
||||
databaseNodeSelected: isDatabaseNodeSelected()
|
||||
},
|
||||
attr:{
|
||||
'aria-expanded': $data.isDatabaseExpanded,
|
||||
'aria-selected': isDatabaseNodeSelected()
|
||||
}"
|
||||
>
|
||||
<span
|
||||
class="databaseId databaseCollChildTextOverflow"
|
||||
data-bind="
|
||||
attr: {
|
||||
title: $data.id()
|
||||
}"
|
||||
>
|
||||
<img
|
||||
class="imgiconwidth collectionsTreeCollapseExpand"
|
||||
src="/Triangle-right.svg"
|
||||
alt="Show database properties"
|
||||
data-bind="visible: !$data.isDatabaseExpanded()"
|
||||
/>
|
||||
<img
|
||||
class="imgiconwidth collectionsTreeCollapseExpand"
|
||||
src="/Triangle-down.svg"
|
||||
alt="Hide database properties"
|
||||
data-bind="visible: $data.isDatabaseExpanded()"
|
||||
/>
|
||||
<img src="/Azure-Cosmos-DB.svg" alt="Database" />
|
||||
<!--ko text: $data.id--><!--/ko-->
|
||||
</span>
|
||||
<span
|
||||
class="menuEllipsis"
|
||||
data-test="databaseEllipsisMenu"
|
||||
name="context menu"
|
||||
role="button"
|
||||
data-bind="
|
||||
click: $data.contextMenu.show,
|
||||
clickBubble: false
|
||||
"
|
||||
>…</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="databaseList" data-test="databaseList" data-bind="visible: $data.isDatabaseExpanded">
|
||||
<!-- Scale & Setings Node - Start -->
|
||||
<div data-bind="visible: $data.isDatabaseShared">
|
||||
<div
|
||||
role="treeitem"
|
||||
class="databaseCollChildTextOverflow treeHovermargin highlight"
|
||||
tabindex="0"
|
||||
data-bind="
|
||||
click: $data.onSettingsClick,
|
||||
event: {
|
||||
keydown: onSettingsKeyDown,
|
||||
keypress: onSettingsKeyPress
|
||||
},
|
||||
css: {
|
||||
highlight: true,
|
||||
collectionNodeSelected: $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid && $data.selectedSubnodeKind() === 11
|
||||
},
|
||||
attr:{
|
||||
'aria-selected': $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid && $data.selectedSubnodeKind() === 11
|
||||
}"
|
||||
>
|
||||
<span class="databaseDocuments"> Scale </span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Scale & Setings Node - End -->
|
||||
<div data-bind="foreach: $data.collections">
|
||||
<collection-node params="{data: $data}"></collection-node>
|
||||
<collection-node-context-menu params="{data: $data}"></collection-node-context-menu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Database Context Menu - Start -->
|
||||
<div data-bind="event: { keydown: onMenuKeyDown }">
|
||||
<div
|
||||
class="context-menu-background"
|
||||
data-bind="
|
||||
visible: $data.contextMenu.visible,
|
||||
click: $data.contextMenu.hide"
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="context-menu"
|
||||
data-test="databaseContextMenu"
|
||||
data-bind="attr:{ tabindex: $data.contextMenu.tabIndex, id: $data.contextMenu.elementId }, visible: $data.contextMenu.visible, foreach: $data.contextMenu.options"
|
||||
>
|
||||
<command-button class="context-menu-option" params="{buttonProps: $data}"></command-button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Database Context Menu - End -->
|
||||
</div>
|
||||
@@ -1,11 +0,0 @@
|
||||
<div
|
||||
class="collectionstree"
|
||||
data-test="resoureTree-collectionsTree"
|
||||
tabindex="0"
|
||||
role="tree"
|
||||
data-bind="attr: { 'aria-label': collectionTitle }"
|
||||
>
|
||||
<div class="databaseList" data-bind="foreach: nonSystemDatabases">
|
||||
<database-node params="{data: $data}"></database-node>
|
||||
</div>
|
||||
</div>
|
||||
@@ -6,7 +6,6 @@ import { TreeComponent, TreeNode, TreeNodeMenuItem } from "../Controls/TreeCompo
|
||||
import * as ViewModels from "../../Contracts/ViewModels";
|
||||
import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem";
|
||||
import { ResourceTreeContextMenuButtonFactory } from "../ContextMenuButtonFactory";
|
||||
import NotebookTab from "../Tabs/NotebookTab";
|
||||
import * as MostRecentActivity from "../MostRecentActivity/MostRecentActivity";
|
||||
import { CosmosClient } from "../../Common/CosmosClient";
|
||||
import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg";
|
||||
@@ -302,7 +301,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
onClick: sp.open.bind(sp),
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.rid, "Collection", ViewModels.CollectionTabKind.StoredProcedures),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createStoreProcedureContextMenuItems(this.container)
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createStoreProcedureContextMenuItems(this.container, sp)
|
||||
})),
|
||||
onClick: () => {
|
||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.StoredProcedures);
|
||||
@@ -319,7 +318,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
onClick: udf.open.bind(udf),
|
||||
isSelected: () =>
|
||||
this.isDataNodeSelected(collection.rid, "Collection", ViewModels.CollectionTabKind.UserDefinedFunctions),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createUserDefinedFunctionContextMenuItems(this.container)
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createUserDefinedFunctionContextMenuItems(this.container, udf)
|
||||
})),
|
||||
onClick: () => {
|
||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.UserDefinedFunctions);
|
||||
@@ -335,7 +334,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
label: trigger.id(),
|
||||
onClick: trigger.open.bind(trigger),
|
||||
isSelected: () => this.isDataNodeSelected(collection.rid, "Collection", ViewModels.CollectionTabKind.Triggers),
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createTriggerContextMenuItems(this.container)
|
||||
contextMenu: ResourceTreeContextMenuButtonFactory.createTriggerContextMenuItems(this.container, trigger)
|
||||
})),
|
||||
onClick: () => {
|
||||
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Triggers);
|
||||
@@ -526,7 +525,10 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
return (
|
||||
activeTab &&
|
||||
activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
|
||||
(activeTab as NotebookTab).notebookPath() === item.path
|
||||
/* TODO Redesign Tab interface so that resource tree doesn't need to know about NotebookV2Tab.
|
||||
NotebookV2Tab could be dynamically imported, but not worth it to just get this type right.
|
||||
*/
|
||||
(activeTab as any).notebookPath() === item.path
|
||||
);
|
||||
},
|
||||
contextMenu: createFileContextMenu
|
||||
@@ -640,7 +642,10 @@ export class ResourceTreeAdapter implements ReactAdapter {
|
||||
return (
|
||||
activeTab &&
|
||||
activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
|
||||
(activeTab as NotebookTab).notebookPath() === item.path
|
||||
/* TODO Redesign Tab interface so that resource tree doesn't need to know about NotebookV2Tab.
|
||||
NotebookV2Tab could be dynamically imported, but not worth it to just get this type right.
|
||||
*/
|
||||
(activeTab as any).notebookPath() === item.path
|
||||
);
|
||||
},
|
||||
contextMenu:
|
||||
|
||||
@@ -5,9 +5,7 @@ import * as DataModels from "../../Contracts/DataModels";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
|
||||
import StoredProcedureTab from "../Tabs/StoredProcedureTab";
|
||||
import ContextMenu from "../Menus/ContextMenu";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { ContextMenuButtonFactory } from "../ContextMenuButtonFactory";
|
||||
|
||||
const sampleStoredProcedureBody: string = `// SAMPLE STORED PROCEDURE
|
||||
function sample(prefix) {
|
||||
@@ -44,7 +42,6 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
|
||||
public rid: string;
|
||||
public id: ko.Observable<string>;
|
||||
public body: ko.Observable<string>;
|
||||
public contextMenu: ViewModels.ContextMenu;
|
||||
public isExecuteEnabled: boolean;
|
||||
|
||||
constructor(container: ViewModels.Explorer, collection: ViewModels.Collection, data: DataModels.StoredProcedure) {
|
||||
@@ -56,9 +53,6 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
|
||||
this.id = ko.observable(data.id);
|
||||
this.body = ko.observable(data.body);
|
||||
this.isExecuteEnabled = this.container.isFeatureEnabled(Constants.Features.executeSproc);
|
||||
|
||||
this.contextMenu = new ContextMenu(this.container, this.rid);
|
||||
this.contextMenu.options(ContextMenuButtonFactory.createStoreProcedureContextMenuButton(container));
|
||||
}
|
||||
|
||||
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
||||
@@ -89,9 +83,6 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
|
||||
|
||||
// Activate
|
||||
storedProcedureTab.onTabClick();
|
||||
|
||||
// Hide Context Menu (if necessary)
|
||||
source.contextMenu.hide(source, event);
|
||||
}
|
||||
|
||||
public select() {
|
||||
@@ -148,10 +139,7 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
|
||||
storedProcedureTab.onTabClick();
|
||||
};
|
||||
|
||||
public delete(source: ViewModels.Collection, event: MouseEvent | KeyboardEvent) {
|
||||
// Hide Context Menu (if necessary)
|
||||
this.contextMenu.hide(source, event);
|
||||
|
||||
public delete() {
|
||||
if (!window.confirm("Are you sure you want to delete the stored procedure?")) {
|
||||
return;
|
||||
}
|
||||
@@ -192,33 +180,6 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
|
||||
});
|
||||
}
|
||||
|
||||
public onKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === "Delete") {
|
||||
this.delete(source, event);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onMenuKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === "Escape") {
|
||||
this.contextMenu.hide(source, event);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === " " || event.key === "Enter") {
|
||||
this.open();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onFocusAfterExecute(): void {
|
||||
const focusElement = document.getElementById("execute-storedproc-toggles");
|
||||
focusElement && focusElement.focus();
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
<div
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
class="pointerCursor"
|
||||
data-bind="
|
||||
click: $data.open,
|
||||
event: {
|
||||
keydown: onKeyDown,
|
||||
keypress: onKeyPress,
|
||||
contextmenu: $data.contextMenu.show
|
||||
},
|
||||
clickBubble: false,
|
||||
contextmenuBubble: false,
|
||||
css: {
|
||||
highlight: true,
|
||||
collectionNodeSelected: $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid,
|
||||
contextmenushowing: $data.contextMenu.visible
|
||||
},
|
||||
attr:{
|
||||
'aria-selected': $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid
|
||||
}"
|
||||
>
|
||||
<div class="storedChildMenu treeChildMenu">
|
||||
<div
|
||||
class="childMenu"
|
||||
data-bind="
|
||||
attr: {
|
||||
title: $data.id()
|
||||
}"
|
||||
>
|
||||
<!--ko text: $data.id-->
|
||||
<!--/ko-->
|
||||
</div>
|
||||
|
||||
<span
|
||||
class="menuEllipsis"
|
||||
name="context menu"
|
||||
role="button"
|
||||
data-bind="
|
||||
click: $data.contextMenu.show,
|
||||
clickBubble: false
|
||||
"
|
||||
>…</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Stored Procedure Node Context Menu - Start -->
|
||||
<div data-bind="event: { keydown: onMenuKeyDown }">
|
||||
<div
|
||||
class="context-menu-background"
|
||||
data-bind="
|
||||
visible: $data.contextMenu.visible,
|
||||
click: $data.contextMenu.hide"
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="context-menu"
|
||||
data-bind="attr:{ tabindex: $data.contextMenu.tabIndex, id: $data.contextMenu.elementId }, visible: $data.contextMenu.visible, foreach: $data.contextMenu.options"
|
||||
>
|
||||
<command-button class="context-menu-option" params="{buttonProps: $data}"></command-button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Stored Procedure Node Context Menu - End -->
|
||||
@@ -1,76 +0,0 @@
|
||||
import resourceTreeTemplate from "./ResourceTree.html";
|
||||
import databaseTreeNoteTemplate from "./DatabaseTreeNode.html";
|
||||
import collectionTreeNodeTemplate from "./CollectionTreeNode.html";
|
||||
import storedProcedureTreeNodeTemplate from "./StoredProcedureTreeNode.html";
|
||||
import userDefinedFunctionTreeNodeTemplate from "./UserDefinedFunctionTreeNode.html";
|
||||
import triggerTreeNodeTemplate from "./TriggerTreeNode.html";
|
||||
import collectionTreeNodeContextMenuTemplate from "./CollectionTreeNodeContextMenu.html";
|
||||
|
||||
export class TreeNodeComponent {
|
||||
constructor(data: any) {
|
||||
return data.data;
|
||||
}
|
||||
}
|
||||
|
||||
export class ResourceTree {
|
||||
constructor() {
|
||||
return {
|
||||
viewModel: TreeNodeComponent,
|
||||
template: resourceTreeTemplate
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class DatabaseTreeNode {
|
||||
constructor() {
|
||||
return {
|
||||
viewModel: TreeNodeComponent,
|
||||
template: databaseTreeNoteTemplate
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CollectionTreeNode {
|
||||
constructor() {
|
||||
return {
|
||||
viewModel: TreeNodeComponent,
|
||||
template: collectionTreeNodeTemplate
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class StoredProcedureTreeNode {
|
||||
constructor() {
|
||||
return {
|
||||
viewModel: TreeNodeComponent,
|
||||
template: storedProcedureTreeNodeTemplate
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class UserDefinedFunctionTreeNode {
|
||||
constructor() {
|
||||
return {
|
||||
viewModel: TreeNodeComponent,
|
||||
template: userDefinedFunctionTreeNodeTemplate
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class TriggerTreeNode {
|
||||
constructor() {
|
||||
return {
|
||||
viewModel: TreeNodeComponent,
|
||||
template: triggerTreeNodeTemplate
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CollectionTreeNodeContextMenu {
|
||||
constructor() {
|
||||
return {
|
||||
viewModel: TreeNodeComponent,
|
||||
template: collectionTreeNodeContextMenuTemplate
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,7 @@ import * as DataModels from "../../Contracts/DataModels";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import Collection from "./Collection";
|
||||
import TriggerTab from "../Tabs/TriggerTab";
|
||||
import ContextMenu from "../Menus/ContextMenu";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { ContextMenuButtonFactory } from "../ContextMenuButtonFactory";
|
||||
|
||||
export default class Trigger implements ViewModels.Trigger {
|
||||
public nodeKind: string;
|
||||
@@ -19,7 +17,6 @@ export default class Trigger implements ViewModels.Trigger {
|
||||
public body: ko.Observable<string>;
|
||||
public triggerType: ko.Observable<string>;
|
||||
public triggerOperation: ko.Observable<string>;
|
||||
public contextMenu: ViewModels.ContextMenu;
|
||||
|
||||
constructor(container: ViewModels.Explorer, collection: ViewModels.Collection, data: any) {
|
||||
this.nodeKind = "Trigger";
|
||||
@@ -31,9 +28,6 @@ export default class Trigger implements ViewModels.Trigger {
|
||||
this.body = ko.observable(data.body);
|
||||
this.triggerOperation = ko.observable(data.triggerOperation);
|
||||
this.triggerType = ko.observable(data.triggerType);
|
||||
|
||||
this.contextMenu = new ContextMenu(this.container, this.rid);
|
||||
this.contextMenu.options(ContextMenuButtonFactory.createTriggerContextMenuButton(container));
|
||||
}
|
||||
|
||||
public select() {
|
||||
@@ -78,9 +72,6 @@ export default class Trigger implements ViewModels.Trigger {
|
||||
|
||||
// Activate
|
||||
triggerTab.onTabClick();
|
||||
|
||||
// Hide Context Menu (if necessary)
|
||||
source.contextMenu.hide(source, event);
|
||||
}
|
||||
|
||||
public open = () => {
|
||||
@@ -125,10 +116,7 @@ export default class Trigger implements ViewModels.Trigger {
|
||||
triggerTab.onTabClick();
|
||||
};
|
||||
|
||||
public delete(source: Collection, event: MouseEvent | KeyboardEvent) {
|
||||
// Hide Context Menu (if necessary)
|
||||
this.contextMenu.hide(source, event);
|
||||
|
||||
public delete() {
|
||||
if (!window.confirm("Are you sure you want to delete the trigger?")) {
|
||||
return;
|
||||
}
|
||||
@@ -150,31 +138,4 @@ export default class Trigger implements ViewModels.Trigger {
|
||||
reason => {}
|
||||
);
|
||||
}
|
||||
|
||||
public onKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === "Delete") {
|
||||
this.delete(source, event);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onMenuKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === "Escape") {
|
||||
this.contextMenu.hide(source, event);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === " " || event.key === "Enter") {
|
||||
this.open();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
<div
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
class="pointerCursor"
|
||||
data-bind="
|
||||
click: $data.open,
|
||||
event: {
|
||||
keydown: onKeyDown,
|
||||
keypress: onKeyPress,
|
||||
contextmenu: $data.contextMenu.show
|
||||
},
|
||||
clickBubble: false,
|
||||
contextmenuBubble: false,
|
||||
css: {
|
||||
highlight: true,
|
||||
collectionNodeSelected: $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid,
|
||||
contextmenushowing: $data.contextMenu.visible
|
||||
},
|
||||
attr:{
|
||||
'aria-selected': $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid
|
||||
}"
|
||||
>
|
||||
<div class="triggersChildMenu treeChildMenu">
|
||||
<div
|
||||
class="childMenu"
|
||||
data-bind="
|
||||
attr: {
|
||||
title: $data.id()
|
||||
}"
|
||||
>
|
||||
<!--ko text: $data.id-->
|
||||
<!--/ko-->
|
||||
</div>
|
||||
<span
|
||||
class="menuEllipsis"
|
||||
name="context menu"
|
||||
role="button"
|
||||
data-bind="
|
||||
click: $data.contextMenu.show,
|
||||
clickBubble: false
|
||||
"
|
||||
>…</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Trigger Node Context Menu - Start -->
|
||||
<div data-bind="event: { keydown: onMenuKeyDown }">
|
||||
<div
|
||||
class="context-menu-background"
|
||||
data-bind="
|
||||
visible: $data.contextMenu.visible,
|
||||
click: $data.contextMenu.hide"
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="context-menu"
|
||||
data-bind="attr:{ tabindex: $data.contextMenu.tabIndex, id: $data.contextMenu.elementId }, visible: $data.contextMenu.visible, foreach: $data.contextMenu.options"
|
||||
>
|
||||
<command-button class="context-menu-option" params="{buttonProps: $data}"></command-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -4,9 +4,7 @@ import * as Constants from "../../Common/Constants";
|
||||
import * as DataModels from "../../Contracts/DataModels";
|
||||
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||
import UserDefinedFunctionTab from "../Tabs/UserDefinedFunctionTab";
|
||||
import ContextMenu from "../Menus/ContextMenu";
|
||||
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||
import { ContextMenuButtonFactory } from "../ContextMenuButtonFactory";
|
||||
import Collection from "./Collection";
|
||||
|
||||
export default class UserDefinedFunction implements ViewModels.UserDefinedFunction {
|
||||
@@ -17,7 +15,6 @@ export default class UserDefinedFunction implements ViewModels.UserDefinedFuncti
|
||||
public rid: string;
|
||||
public id: ko.Observable<string>;
|
||||
public body: ko.Observable<string>;
|
||||
public contextMenu: ViewModels.ContextMenu;
|
||||
|
||||
constructor(container: ViewModels.Explorer, collection: ViewModels.Collection, data: DataModels.UserDefinedFunction) {
|
||||
this.nodeKind = "UserDefinedFunction";
|
||||
@@ -28,9 +25,6 @@ export default class UserDefinedFunction implements ViewModels.UserDefinedFuncti
|
||||
this.rid = data._rid;
|
||||
this.id = ko.observable(data.id);
|
||||
this.body = ko.observable(data.body);
|
||||
|
||||
this.contextMenu = new ContextMenu(this.container, this.rid);
|
||||
this.contextMenu.options(ContextMenuButtonFactory.createUserDefinedFunctionContextMenuButton(container));
|
||||
}
|
||||
|
||||
public static create(source: ViewModels.Collection, event: MouseEvent) {
|
||||
@@ -61,9 +55,6 @@ export default class UserDefinedFunction implements ViewModels.UserDefinedFuncti
|
||||
|
||||
// Activate
|
||||
userDefinedFunctionTab.onTabClick();
|
||||
|
||||
// Hide Context Menu (if necessary)
|
||||
source.contextMenu.hide(source, event);
|
||||
}
|
||||
|
||||
public open = () => {
|
||||
@@ -115,10 +106,7 @@ export default class UserDefinedFunction implements ViewModels.UserDefinedFuncti
|
||||
});
|
||||
}
|
||||
|
||||
public delete(source: Collection, event: MouseEvent | KeyboardEvent) {
|
||||
// Hide Context Menu (if necessary)
|
||||
this.contextMenu.hide(source, event);
|
||||
|
||||
public delete() {
|
||||
if (!window.confirm("Are you sure you want to delete the user defined function?")) {
|
||||
return;
|
||||
}
|
||||
@@ -137,31 +125,4 @@ export default class UserDefinedFunction implements ViewModels.UserDefinedFuncti
|
||||
reason => {}
|
||||
);
|
||||
}
|
||||
|
||||
public onKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === "Delete") {
|
||||
this.delete(source, event);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onMenuKeyDown = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === "Escape") {
|
||||
this.contextMenu.hide(source, event);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.key === " " || event.key === "Enter") {
|
||||
this.open();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
<div
|
||||
role="treeitem"
|
||||
tabindex="0"
|
||||
class="pointerCursor"
|
||||
data-bind="
|
||||
click: $data.open,
|
||||
event: {
|
||||
keydown: onKeyDown,
|
||||
keypress: onKeyPress,
|
||||
contextmenu: $data.contextMenu.show
|
||||
},
|
||||
clickBubble: false,
|
||||
contextmenuBubble: false,
|
||||
css: {
|
||||
highlight: true,
|
||||
collectionNodeSelected: $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid,
|
||||
contextmenushowing: $data.contextMenu.visible
|
||||
},
|
||||
attr:{
|
||||
'aria-selected': $root.selectedNode && $root.selectedNode() && $root.selectedNode().rid === $data.rid
|
||||
}"
|
||||
>
|
||||
<div class="userDefinedchildMenu treeChildMenu">
|
||||
<div
|
||||
class="childMenu"
|
||||
data-bind="
|
||||
attr: {
|
||||
title: $data.id()
|
||||
}"
|
||||
>
|
||||
<!--ko text: $data.id-->
|
||||
<!--/ko-->
|
||||
</div>
|
||||
<span
|
||||
class="menuEllipsis"
|
||||
name="context menu"
|
||||
role="button"
|
||||
data-bind="
|
||||
click: $data.contextMenu.show,
|
||||
clickBubble: false
|
||||
"
|
||||
>…</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- User Defined Function Node Context Menu - Start -->
|
||||
<div data-bind="event: { keydown: onMenuKeyDown }">
|
||||
<div
|
||||
class="context-menu-background"
|
||||
data-bind="
|
||||
visible: $data.contextMenu.visible,
|
||||
click: $data.contextMenu.hide"
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="context-menu"
|
||||
data-bind="attr:{ tabindex: $data.contextMenu.tabIndex, id: $data.contextMenu.elementId }, visible: $data.contextMenu.visible, foreach: $data.contextMenu.options"
|
||||
>
|
||||
<command-button class="context-menu-option" params="{buttonProps: $data}"></command-button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- User Defined Function Node Context Menu - End -->
|
||||
@@ -7,7 +7,6 @@ import "../less/menus.less";
|
||||
import "../less/infobox.less";
|
||||
import "../less/messagebox.less";
|
||||
import "./Explorer/Controls/InputTypeahead/InputTypeahead.less";
|
||||
import "./Explorer/Controls/CommandButton/CommandButton.less";
|
||||
import "./Explorer/Controls/ErrorDisplayComponent/ErrorDisplayComponent.less";
|
||||
import "./Explorer/Menus/NotificationConsole/NotificationConsole.less";
|
||||
import "./Explorer/Menus/CommandBar/CommandBarComponent.less";
|
||||
|
||||
@@ -5,8 +5,6 @@ import * as Constants from "../Common/Constants";
|
||||
import Explorer from "../Explorer/Explorer";
|
||||
import { TabRouteHandler } from "./TabRouteHandler";
|
||||
|
||||
jest.mock("../Explorer/Tabs/NotebookTab");
|
||||
|
||||
describe("TabRouteHandler", () => {
|
||||
let tabRouteHandler: TabRouteHandler;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Defines constants related to logging telemetry. This file should be kept in sync with the one in the portal extension code as much as possible.
|
||||
* Defines constants related to logging telemetry. Everything except Action should be kept in sync with the one in the Portal code as much as possible.
|
||||
*
|
||||
* TODO: Move this to ExplorerContracts (265329)
|
||||
*/
|
||||
@@ -8,13 +8,9 @@ export class General {
|
||||
public static BladeNamePrefix: string = "Extension/Microsoft_Azure_DocumentDB/Blade/";
|
||||
}
|
||||
|
||||
/**
|
||||
* This is to be kept in sync with the one in portal. Please update the one in the portal if you add/remove any entry.
|
||||
*/
|
||||
// Data Explorer specific actions. No need to keep this in sync with the one in Portal.
|
||||
export enum Action {
|
||||
CollapseTreeNode,
|
||||
CreateDatabaseAccount,
|
||||
CreateAzureFunction,
|
||||
CreateCollection,
|
||||
CreateDocument,
|
||||
CreateStoredProcedure,
|
||||
@@ -23,7 +19,6 @@ export enum Action {
|
||||
DeleteCollection,
|
||||
DeleteDatabase,
|
||||
DeleteDocument,
|
||||
DownloadQuickstart,
|
||||
ExpandTreeNode,
|
||||
ExecuteQuery,
|
||||
HasFeature,
|
||||
@@ -33,27 +28,18 @@ export enum Action {
|
||||
LoadDatabaseAccount,
|
||||
LoadCollections,
|
||||
LoadDatabases,
|
||||
LoadMetrics,
|
||||
LoadOffers,
|
||||
LoadSingleCollectionWithOfferAndStatistics,
|
||||
MongoShell,
|
||||
OpenMetrics,
|
||||
ContextualPane,
|
||||
ScaleThroughput,
|
||||
SelectItem,
|
||||
SwitchQuickstartPlatform,
|
||||
Tab,
|
||||
UpdateDocument,
|
||||
UpdateRegions,
|
||||
UpdateSettings,
|
||||
UpdateStoredProcedure,
|
||||
UpdateTrigger,
|
||||
UpdateUDF,
|
||||
ViewWarning,
|
||||
LoadBlade,
|
||||
LoadResourceTree,
|
||||
LoadMetricsTab,
|
||||
AccountLevelThroughput,
|
||||
CreateDatabase,
|
||||
ResolveConflict,
|
||||
DeleteConflict,
|
||||
|
||||
@@ -66,8 +66,6 @@ c.NotebookApp.disable_check_xsrf = True
|
||||
* There is a "New Cell" button in the CommandBar outside the jupyter iframe which will add a cell inside the notebook.
|
||||
|
||||
# Notes
|
||||
* The iframe in the Data Explorer Tab loads jupyter with the server and notebook pathname passed in the query parameters (they're hardcoded right now):
|
||||
cosmosdb-dataexplorer/Product/Portal/DataExplorer/src/Explorer/Tabs/NotebookTab.html
|
||||
* The Emulator is located in: C:\Program Files\Azure Cosmos DB Emulator\Packages\DataExplorer
|
||||
* Running "jupyter notebook" serves the jupyter traditional frontend. There is an alternate frontend also developed by jupyter which is modular and customizable called: JupyterLab. We use their "notebook" example in this project slightly modified to pass the server and notebook pathname via iframe url's parameters:
|
||||
https://github.com/jupyterlab/jupyterlab/tree/master/examples/notebook
|
||||
|
||||
@@ -128,15 +128,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- Collections Window Title/Command Bar - End -->
|
||||
<!-- ko if: !enableLegacyResourceTree() && !isAuthWithResourceToken() -->
|
||||
<!-- ko if: !isAuthWithResourceToken() -->
|
||||
<div style="overflow-y: auto" data-bind="react:resourceTree"></div>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: !enableLegacyResourceTree() && isAuthWithResourceToken() -->
|
||||
<!-- ko if: isAuthWithResourceToken() -->
|
||||
<div style="overflow-y: auto" data-bind="react:resourceTreeForResourceToken"></div>
|
||||
<!-- /ko -->
|
||||
<!-- ko if: enableLegacyResourceTree -->
|
||||
<resource-tree class="resourceTreeScroll" params="{data: $data}"></resource-tree>
|
||||
<!-- /ko -->
|
||||
</div>
|
||||
<!-- Collections Window - End -->
|
||||
</div>
|
||||
@@ -337,10 +334,6 @@
|
||||
<conflicts-tab params="{data: $data}"></conflicts-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 13 -->
|
||||
<notebook-tab params="{data: $data}"></notebook-tab>
|
||||
<!-- /ko -->
|
||||
|
||||
<!-- ko if: $data.tabKind === 14 -->
|
||||
<terminal-tab params="{data: $data}"></terminal-tab>
|
||||
<!-- /ko -->
|
||||
@@ -431,7 +424,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- Global loader - End -->
|
||||
|
||||
<div data-bind="react:uploadItemsPaneAdapter"></div>
|
||||
<add-database-pane params="{data: addDatabasePane}"></add-database-pane>
|
||||
<add-collection-pane params="{data: addCollectionPane}"></add-collection-pane>
|
||||
<delete-collection-confirmation-pane params="{data: deleteCollectionConfirmationPane}">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DatabaseAccount } from "../../Contracts/DataModels";
|
||||
import { PlatformType } from "../../PlatformType";
|
||||
import { Platform } from "../../Config";
|
||||
|
||||
export interface StartUploadMessageParams {
|
||||
files: FileList;
|
||||
@@ -12,7 +12,7 @@ export interface DocumentClientParams {
|
||||
masterKey: string;
|
||||
endpoint: string;
|
||||
accessToken: string;
|
||||
platform: PlatformType;
|
||||
platform: Platform;
|
||||
databaseAccount: DatabaseAccount;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import "babel-polyfill";
|
||||
import { DocumentClientParams, UploadDetailsRecord, UploadDetails } from "./definitions";
|
||||
import { CosmosClient } from "../../Common/CosmosClient";
|
||||
import { config } from "../../Config";
|
||||
|
||||
let numUploadsSuccessful = 0;
|
||||
let numUploadsFailed = 0;
|
||||
@@ -33,8 +34,7 @@ onmessage = (event: MessageEvent) => {
|
||||
CosmosClient.endpoint(clientParams.endpoint);
|
||||
CosmosClient.accessToken(clientParams.accessToken);
|
||||
CosmosClient.databaseAccount(clientParams.databaseAccount);
|
||||
self.dataExplorerPlatform = clientParams.platform;
|
||||
console.log(event);
|
||||
config.platform = clientParams.platform;
|
||||
if (!!files && files.length > 0) {
|
||||
numFiles = files.length;
|
||||
for (let i = 0; i < numFiles; i++) {
|
||||
@@ -87,7 +87,7 @@ function createDocumentsFromFile(fileName: string, documentContent: string): voi
|
||||
numUploadsSuccessful++;
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
recordUploadDetailErrorForFile(fileName, JSON.stringify(error));
|
||||
numUploadsFailed++;
|
||||
})
|
||||
@@ -106,6 +106,7 @@ function createDocumentsFromFile(fileName: string, documentContent: string): voi
|
||||
triggerCreateDocument(content);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
recordUploadDetailErrorForFile(fileName, e.message);
|
||||
transmitResultIfUploadComplete();
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
"./src/Contracts/Versions.ts",
|
||||
"./src/Controls/Heatmap/HeatmapDatatypes.ts",
|
||||
"./src/Definitions/plotly.js-cartesian-dist.d-min.ts",
|
||||
"./src/Explorer/Controls/Notebook/NotebookAppMessageHandler.ts",
|
||||
"./src/Explorer/Controls/Toolbar/IToolbarAction.ts",
|
||||
"./src/Explorer/Controls/Toolbar/IToolbarDisplayable.ts",
|
||||
"./src/Explorer/Controls/Toolbar/IToolbarDropDown.ts",
|
||||
|
||||
Reference in New Issue
Block a user