Compare commits

..

4 Commits

Author SHA1 Message Date
Steve Faulkner
3951f01422 More robust fix 2020-06-09 16:29:41 -05:00
Steve Faulkner
b62cd98f67 Merge branch 'firefox-emulator-fix' into fix-emulator-upload 2020-06-09 15:15:10 -05:00
Steve Faulkner
0240eac920 Fix emulator upload by passing down config.platform 2020-06-09 14:51:30 -05:00
Steve Faulkner
5fb2fe2798 Update to latest JS SDK. Fixes Firefox emulator bug. Close #12 2020-06-08 12:25:10 -05:00
497 changed files with 51808 additions and 58560 deletions

View File

@@ -1,6 +0,0 @@
# These options are only needed when if running end to end tests locally
PORTAL_RUNNER_USERNAME=
PORTAL_RUNNER_PASSWORD=
PORTAL_RUNNER_SUBSCRIPTION=
PORTAL_RUNNER_RESOURCE_GROUP=
PORTAL_RUNNER_DATABASE_ACCOUNT=

View File

@@ -1,5 +1,4 @@
**/node_modules/ **/node_modules/
dist/
src/Api/Apis.ts src/Api/Apis.ts
src/AuthType.ts src/AuthType.ts
src/Bindings/BindingHandlersRegisterer.ts src/Bindings/BindingHandlersRegisterer.ts
@@ -26,6 +25,7 @@ src/Common/Logger.test.ts
src/Common/MessageHandler.test.ts src/Common/MessageHandler.test.ts
src/Common/MessageHandler.ts src/Common/MessageHandler.ts
src/Common/MongoProxyClient.test.ts src/Common/MongoProxyClient.test.ts
src/Common/MongoProxyClient.ts
src/Common/MongoUtility.ts src/Common/MongoUtility.ts
src/Common/NotificationsClientBase.ts src/Common/NotificationsClientBase.ts
src/Common/ObjectCache.test.ts src/Common/ObjectCache.test.ts
@@ -202,6 +202,7 @@ src/Explorer/Tabs/GraphTab.ts
src/Explorer/Tabs/MongoDocumentsTab.ts src/Explorer/Tabs/MongoDocumentsTab.ts
src/Explorer/Tabs/MongoQueryTab.ts src/Explorer/Tabs/MongoQueryTab.ts
src/Explorer/Tabs/MongoShellTab.ts src/Explorer/Tabs/MongoShellTab.ts
src/Explorer/Tabs/NotebookTab.ts
src/Explorer/Tabs/NotebookV2Tab.ts src/Explorer/Tabs/NotebookV2Tab.ts
src/Explorer/Tabs/QueryTab.test.ts src/Explorer/Tabs/QueryTab.test.ts
src/Explorer/Tabs/QueryTab.ts src/Explorer/Tabs/QueryTab.ts
@@ -215,6 +216,7 @@ src/Explorer/Tabs/TabComponents.ts
src/Explorer/Tabs/TabsBase.ts src/Explorer/Tabs/TabsBase.ts
src/Explorer/Tabs/TriggerTab.ts src/Explorer/Tabs/TriggerTab.ts
src/Explorer/Tabs/UserDefinedFunctionTab.ts src/Explorer/Tabs/UserDefinedFunctionTab.ts
src/Explorer/Tabs/__mocks__/NotebookTab.ts
src/Explorer/Tree/AccessibleVerticalList.ts src/Explorer/Tree/AccessibleVerticalList.ts
src/Explorer/Tree/Collection.test.ts src/Explorer/Tree/Collection.test.ts
src/Explorer/Tree/Collection.ts src/Explorer/Tree/Collection.ts

View File

@@ -1,44 +1,45 @@
module.exports = { module.exports = {
env: { env: {
browser: true, browser: true,
es6: true, es6: true
}, },
plugins: ["@typescript-eslint", "no-null"], plugins: ["@typescript-eslint"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], extends: [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
globals: { globals: {
Atomics: "readonly", Atomics: "readonly",
SharedArrayBuffer: "readonly", SharedArrayBuffer: "readonly"
}, },
parser: "@typescript-eslint/parser", parser: "@typescript-eslint/parser",
parserOptions: { parserOptions: {
ecmaFeatures: { ecmaFeatures: {
jsx: true, jsx: true
}, },
ecmaVersion: 2018, ecmaVersion: 2018,
sourceType: "module", sourceType: "module"
}, },
overrides: [ overrides: [
{ {
files: ["**/*.tsx"], files: ["**/*.tsx"],
env: { env: {
jest: true, jest: true
}, },
extends: ["plugin:react/recommended"], extends: ["plugin:react/recommended"],
plugins: ["react"], plugins: ["react"]
}, },
{ {
files: ["**/*.test.{ts,tsx}"], files: ["**/*.test.{ts,tsx}"],
env: { env: {
jest: true, jest: true
}, },
extends: ["plugin:jest/recommended"], extends: ["plugin:jest/recommended"],
plugins: ["jest"], plugins: ["jest"]
}, }
], ],
rules: { rules: {
curly: "error", curly: "error"
"@typescript-eslint/no-unused-vars": "error", }
"@typescript-eslint/no-extraneous-class": "error",
"no-null/no-null": "error",
},
}; };

View File

@@ -1,3 +1,6 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: CI name: CI
on: on:
push: push:
@@ -5,41 +8,7 @@ on:
pull_request: pull_request:
branches: [master] branches: [master]
jobs: jobs:
compile: test:
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 runs-on: ubuntu-latest
name: "Unit Tests" name: "Unit Tests"
steps: steps:
@@ -67,14 +36,13 @@ jobs:
path: .cache path: .cache
key: ${{ runner.os }}-build-cache key: ${{ runner.os }}-build-cache
- run: npm run pack:prod - run: npm run pack:prod
- run: cp -r ./Contracts ./dist/contracts - run: npm run copyToConsumers
- run: cp -r ./configs ./dist/configs
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
with: with:
name: dist name: dist
path: dist/ path: dist/
endtoendemulator: endtoend:
name: "End To End Tests | Emulator | SQL" name: "End to End Tests"
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -99,65 +67,9 @@ jobs:
EMULATOR_ENDPOINT: https://0.0.0.0:8081/ EMULATOR_ENDPOINT: https://0.0.0.0:8081/
NODE_TLS_REJECT_UNAUTHORIZED: 0 NODE_TLS_REJECT_UNAUTHORIZED: 0
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress 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: nuget:
name: Publish Nuget name: Publish Nuget
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo] needs: [build, test, endtoend]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }} NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
@@ -170,7 +82,6 @@ jobs:
uses: actions/download-artifact@v2 uses: actions/download-artifact@v2
with: with:
name: dist name: dist
- run: cp ./configs/prod.json config.json
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "GitHub" -Password "$AZURE_DEVOPS_PAT" - run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "GitHub" -Password "$AZURE_DEVOPS_PAT"
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}" - run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
- run: nuget push -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg - run: nuget push -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
@@ -179,7 +90,7 @@ jobs:
path: "*.nupkg" path: "*.nupkg"
nugetmpac: nugetmpac:
name: Publish Nuget MPAC name: Publish Nuget MPAC
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo] needs: [build, test, endtoend]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }} NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
@@ -192,7 +103,6 @@ jobs:
uses: actions/download-artifact@v2 uses: actions/download-artifact@v2
with: with:
name: dist name: dist
- run: cp ./configs/mpac.json config.json
- run: sed -i 's/Azure.Cosmos.DB.Data.Explorer/Azure.Cosmos.DB.Data.Explorer.MPAC/g' DataExplorer.nuspec - run: sed -i 's/Azure.Cosmos.DB.Data.Explorer/Azure.Cosmos.DB.Data.Explorer.MPAC/g' DataExplorer.nuspec
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "GitHub" -Password "$AZURE_DEVOPS_PAT" - run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "GitHub" -Password "$AZURE_DEVOPS_PAT"
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}" - run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"

View File

@@ -1,20 +0,0 @@
name: Runners
on:
schedule:
- cron: "*/10 * * * *"
jobs:
sqlcreatecollection:
runs-on: ubuntu-latest
name: "SQL | Create Collection"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- run: npm ci
- run: npm run test:e2e
env:
PORTAL_RUNNER_APP_INSIGHTS_KEY: ${{ secrets.PORTAL_RUNNER_APP_INSIGHTS_KEY }}
PORTAL_RUNNER_USERNAME: ${{ secrets.PORTAL_RUNNER_USERNAME }}
PORTAL_RUNNER_PASSWORD: ${{ secrets.PORTAL_RUNNER_PASSWORD }}
PORTAL_RUNNER_SUBSCRIPTION: 69e02f2d-f059-4409-9eac-97e8a276ae2c
PORTAL_RUNNER_RESOURCE_GROUP: runners
PORTAL_RUNNER_DATABASE_ACCOUNT: portal-sql-runner

1
.gitignore vendored
View File

@@ -16,4 +16,3 @@ notebookapp/*
Contracts/* Contracts/*
.DS_Store .DS_Store
.cache/ .cache/
.env

View File

@@ -1,51 +0,0 @@
# 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)

View File

@@ -70,7 +70,7 @@ Unit tests are located adjacent to the code under test and run with [Jest](https
`npm run test` `npm run test`
#### End to End CI Tests #### End to End Tests
[Cypress](https://www.cypress.io/) is used for end to end tests and are contained in `cypress/`. Currently, it operates as sub project with its own typescript config and dependencies. It also only operates against the emulator. To run cypress tests: [Cypress](https://www.cypress.io/) is used for end to end tests and are contained in `cypress/`. Currently, it operates as sub project with its own typescript config and dependencies. It also only operates against the emulator. To run cypress tests:
@@ -80,13 +80,16 @@ Unit tests are located adjacent to the code under test and run with [Jest](https
4. Install dependencies: `npm install` 4. Install dependencies: `npm install`
5. Run cypress headless(`npm run test`) or in interactive mode(`npm run test:debug`) 5. Run cypress headless(`npm run test`) or in interactive mode(`npm run test:debug`)
#### End to End Production Runners
Jest and Puppeteer are used for end to end production runners and are contained in `test/`. To run these tests locally:
1. Copy .env.example to .env and fill in all variables
2. Run `npm run test:e2e`
# Contributing # Contributing
Please read the [contribution guidelines](./CONTRIBUTING.md). 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.

View File

@@ -1,3 +1,3 @@
module.exports = { module.exports = {
presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-react", "@babel/preset-typescript"], presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-react", "@babel/preset-typescript"]
}; };

View File

@@ -1,3 +0,0 @@
{
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com"
}

View File

@@ -1,3 +0,0 @@
{
"JUNO_ENDPOINT": "https://tools.cosmos.azure.com"
}

3
cypress/.gitignore vendored
View File

@@ -1,4 +1 @@
cypress.env.json cypress.env.json
cypress/report
cypress/screenshots
cypress/videos

View File

@@ -1,51 +0,0 @@
// 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);
});

View File

@@ -23,7 +23,7 @@ context("Cassandra API Test - createDatabase", () => {
const keyspaceId = `KeyspaceId${crypt.randomBytes(8).toString("hex")}`; const keyspaceId = `KeyspaceId${crypt.randomBytes(8).toString("hex")}`;
const tableId = `TableId112`; const tableId = `TableId112`;
cy.get("iframe").then(($element) => { cy.get("iframe").then($element => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@@ -32,15 +32,27 @@ context("Cassandra API Test - createDatabase", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]'); cy.wrap($body)
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body).find('input[id="keyspace-id"]').should("be.visible").type(keyspaceId); cy.wrap($body)
.find('input[id="keyspace-id"]')
.should("be.visible")
.type(keyspaceId);
cy.wrap($body).find('input[class="textfontclr"]').type(tableId); cy.wrap($body)
.find('input[class="textfontclr"]')
.type(tableId);
cy.wrap($body).find('input[data-test="databaseThroughputValue"]').should("have.value", "400"); cy.wrap($body)
.find('input[data-test="databaseThroughputValue"]')
.should("have.value", "400");
cy.wrap($body).find('data-test="addCollection-createCollection"').click(); cy.wrap($body)
.find('data-test="addCollection-createCollection"')
.click();
cy.wait(10000); cy.wait(10000);

View File

@@ -24,7 +24,7 @@ context("Graph API Test", () => {
const graphId = `TestGraph${crypt.randomBytes(8).toString("hex")}`; const graphId = `TestGraph${crypt.randomBytes(8).toString("hex")}`;
const partitionKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; const partitionKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then(($element) => { cy.get("iframe").then($element => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@@ -33,21 +33,39 @@ context("Graph API Test", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]'); cy.wrap($body)
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body).find('input[data-test="addCollection-createNewDatabase"]').check(); cy.wrap($body)
.find('input[data-test="addCollection-createNewDatabase"]')
.check();
cy.wrap($body).find('input[data-test="addCollection-newDatabaseId"]').should("be.visible").type(dbId); cy.wrap($body)
.find('input[data-test="addCollection-newDatabaseId"]')
.should("be.visible")
.type(dbId);
cy.wrap($body).find('input[data-test="addCollectionPane-databaseSharedThroughput"]').check(); cy.wrap($body)
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
.check();
cy.wrap($body).find('input[data-test="databaseThroughputValue"]').should("have.value", "400"); cy.wrap($body)
.find('input[data-test="databaseThroughputValue"]')
.should("have.value", "400");
cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(graphId); cy.wrap($body)
.find('input[data-test="addCollection-collectionId"]')
.type(graphId);
cy.wrap($body).find('input[data-test="addCollection-partitionKeyValue"]').type(partitionKey); cy.wrap($body)
.find('input[data-test="addCollection-partitionKeyValue"]')
.type(partitionKey);
cy.wrap($body).find('input[data-test="addCollection-createCollection"]').click(); cy.wrap($body)
.find('input[data-test="addCollection-createCollection"]')
.click();
cy.wait(10000); cy.wait(10000);

View File

@@ -16,7 +16,7 @@ let crypt = require("crypto");
context("Mongo API Test - createDatabase", () => { context("Mongo API Test - createDatabase", () => {
beforeEach(() => { beforeEach(() => {
connectionString.loginUsingConnectionString(); connectionString.loginUsingConnectionString(connectionString.constants.mongo);
}); });
it("Create a new collection in Mongo API", () => { it("Create a new collection in Mongo API", () => {
@@ -24,7 +24,7 @@ context("Mongo API Test - createDatabase", () => {
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`; const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then(($element) => { cy.get("iframe").then($element => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@@ -33,21 +33,38 @@ context("Mongo API Test - createDatabase", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]'); cy.wrap($body)
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body).find('input[data-test="addCollection-createNewDatabase"]').check(); cy.wrap($body)
.find('input[data-test="addCollection-createNewDatabase"]')
.check();
cy.wrap($body).find('input[data-test="addCollection-newDatabaseId"]').type(dbId); cy.wrap($body)
.find('input[data-test="addCollection-newDatabaseId"]')
.type(dbId);
cy.wrap($body).find('input[data-test="addCollectionPane-databaseSharedThroughput"]').check(); cy.wrap($body)
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
.check();
cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(collectionId); cy.wrap($body)
.find('input[data-test="addCollection-collectionId"]')
.type(collectionId);
cy.wrap($body).find('input[data-test="databaseThroughputValue"]').should("have.value", "400"); cy.wrap($body)
.find('input[data-test="databaseThroughputValue"]')
.should("have.value", "400");
cy.wrap($body).find('input[data-test="addCollection-partitionKeyValue"]').type(sharedKey); cy.wrap($body)
.find('input[data-test="addCollection-partitionKeyValue"]')
.type(sharedKey);
cy.wrap($body).find("#submitBtnAddCollection").click(); cy.wrap($body)
.find('input[data-test="addCollection-createCollection"]')
.click();
cy.wait(10000); cy.wait(10000);

View File

@@ -16,15 +16,15 @@ let crypt = require("crypto");
context("Mongo API Test", () => { context("Mongo API Test", () => {
beforeEach(() => { beforeEach(() => {
connectionString.loginUsingConnectionString(); connectionString.loginUsingConnectionString(connectionString.constants.mongo);
}); });
it.skip("Create a new collection in Mongo API - Autopilot", () => { it("Create a new collection in Mongo API - Autopilot", () => {
const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`; const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`;
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`; const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then(($element) => { cy.get("iframe").then($element => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@@ -33,23 +33,34 @@ context("Mongo API Test", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]'); cy.wrap($body)
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body).find('input[data-test="addCollection-createNewDatabase"]').check(); cy.wrap($body)
.find('input[data-test="addCollection-createNewDatabase"]')
.check();
cy.wrap($body).find('input[data-test="addCollection-newDatabaseId"]').type(dbId); cy.wrap($body)
.find('input[data-test="addCollection-newDatabaseId"]')
.type(dbId);
cy.wrap($body).find('input[data-test="addCollectionPane-databaseSharedThroughput"]').check(); cy.wrap($body)
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
.check();
cy.wrap($body) cy.wrap($body)
.find('div[class="throughputModeContainer"]') .find('div[class="throughputModeContainer"]')
.should("be.visible") .should("be.visible")
.and((input) => { .and(input => {
expect(input.get(0).textContent, "first item").contains("Autopilot (preview)"); expect(input.get(0).textContent, "first item").contains("Autopilot (preview)");
expect(input.get(1).textContent, "second item").contains("Manual"); expect(input.get(1).textContent, "second item").contains("Manual");
}); });
cy.wrap($body).find('input[id="newContainer-databaseThroughput-autoPilotRadio"]').check(); cy.wrap($body)
.find('input[id="newContainer-databaseThroughput-autoPilotRadio"]')
.check();
cy.wrap($body) cy.wrap($body)
.find('select[name="autoPilotTiers"]') .find('select[name="autoPilotTiers"]')
@@ -57,13 +68,19 @@ context("Mongo API Test", () => {
// // .select('4,000 RU/s').should('have.value', '1'); // // .select('4,000 RU/s').should('have.value', '1');
.find('option[value="2"]') .find('option[value="2"]')
.then(($element) => $element.get(1).setAttribute("selected", "selected")); .then($element => $element.get(1).setAttribute("selected", "selected"));
cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(collectionId); cy.wrap($body)
.find('input[data-test="addCollection-collectionId"]')
.type(collectionId);
cy.wrap($body).find('input[data-test="addCollection-partitionKeyValue"]').type(sharedKey); cy.wrap($body)
.find('input[data-test="addCollection-partitionKeyValue"]')
.type(sharedKey);
cy.wrap($body).find('input[data-test="addCollection-createCollection"]').click(); cy.wrap($body)
.find('input[data-test="addCollection-createCollection"]')
.click();
cy.wait(10000); cy.wait(10000);

View File

@@ -4,20 +4,20 @@ let crypt = require("crypto");
context("Mongo API Test", () => { context("Mongo API Test", () => {
beforeEach(() => { beforeEach(() => {
connectionString.loginUsingConnectionString(); connectionString.loginUsingConnectionString(connectionString.constants.mongo);
}); });
it.skip("Create a new collection in existing database in Mongo API", () => { it("Create a new collection in existing database in Mongo API", () => {
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`; const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then(($element) => { cy.get("iframe").then($element => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('span[class="nodeLabel"]') .find('span[class="nodeLabel"]')
.should("be.visible") .should("be.visible")
.then(($span) => { .then($span => {
const dbId1 = $span.text(); const dbId1 = $span.text();
cy.log("DBBB", dbId1); cy.log("DBBB", dbId1);
@@ -28,17 +28,30 @@ context("Mongo API Test", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]'); cy.wrap($body)
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body).find('input[data-test="addCollection-existingDatabase"]').check(); cy.wrap($body)
.find('input[data-test="addCollection-existingDatabase"]')
.check();
cy.wrap($body).find('input[data-test="addCollection-existingDatabase"]').type(dbId1); cy.wrap($body)
.find('input[data-test="addCollection-existingDatabase"]')
.type(dbId1);
cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(collectionId); cy.wrap($body)
.find('input[data-test="addCollection-collectionId"]')
.type(collectionId);
cy.wrap($body).find('input[data-test="addCollection-partitionKeyValue"]').type(sharedKey); cy.wrap($body)
.find('input[data-test="addCollection-partitionKeyValue"]')
.type(sharedKey);
cy.wrap($body).find('input[data-test="addCollection-createCollection"]').click(); cy.wrap($body)
.find('input[data-test="addCollection-createCollection"]')
.click();
cy.wait(10000); cy.wait(10000);

View File

@@ -2,9 +2,9 @@ const connectionString = require("../../../utilities/connectionString");
let crypt = require("crypto"); let crypt = require("crypto");
context.skip("Mongo API Test", () => { context("Mongo API Test", () => {
beforeEach(() => { beforeEach(() => {
connectionString.loginUsingConnectionString(); connectionString.loginUsingConnectionString(connectionString.constants.mongo);
}); });
it("Create a new collection in Mongo API - Provision database throughput", () => { it("Create a new collection in Mongo API - Provision database throughput", () => {
@@ -12,7 +12,7 @@ context.skip("Mongo API Test", () => {
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`; const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then(($element) => { cy.get("iframe").then($element => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@@ -21,31 +21,50 @@ context.skip("Mongo API Test", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]'); cy.wrap($body)
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body) cy.wrap($body)
.find(".createNewDatabaseOrUseExisting") .find(".createNewDatabaseOrUseExisting")
.should("have.length", 2) .should("have.length", 2)
.and((input) => { .and(input => {
expect(input.get(0).textContent, "first item").contains("Create new"); expect(input.get(0).textContent, "first item").contains("Create new");
expect(input.get(1).textContent, "second item").contains("Use existing"); expect(input.get(1).textContent, "second item").contains("Use existing");
}); });
cy.wrap($body).find('input[data-test="addCollection-createNewDatabase"]').check(); cy.wrap($body)
.find('input[data-test="addCollection-createNewDatabase"]')
.check();
cy.wrap($body).find('input[data-test="addCollectionPane-databaseSharedThroughput"]').check(); cy.wrap($body)
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
.check();
cy.wrap($body).find('input[data-test="addCollection-newDatabaseId"]').type(dbId); cy.wrap($body)
.find('input[data-test="addCollection-newDatabaseId"]')
.type(dbId);
cy.wrap($body).find('input[data-test="addCollectionPane-databaseSharedThroughput"]').check(); cy.wrap($body)
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
.check();
cy.wrap($body).find('input[data-test="databaseThroughputValue"]').should("have.value", "400"); cy.wrap($body)
.find('input[data-test="databaseThroughputValue"]')
.should("have.value", "400");
cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(collectionId); cy.wrap($body)
.find('input[data-test="addCollection-collectionId"]')
.type(collectionId);
cy.wrap($body).find('input[data-test="addCollection-partitionKeyValue"]').type(sharedKey); cy.wrap($body)
.find('input[data-test="addCollection-partitionKeyValue"]')
.type(sharedKey);
cy.wrap($body).find('input[data-test="addCollection-createCollection"]').click(); cy.wrap($body)
.find('input[data-test="addCollection-createCollection"]')
.click();
cy.wait(10000); cy.wait(10000);
@@ -65,7 +84,7 @@ context.skip("Mongo API Test", () => {
const collectionIdTitle = `Add Collection`; const collectionIdTitle = `Add Collection`;
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then(($element) => { cy.get("iframe").then($element => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@@ -74,23 +93,42 @@ context.skip("Mongo API Test", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]'); cy.wrap($body)
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body).find('input[data-test="addCollection-createNewDatabase"]').check(); cy.wrap($body)
.find('input[data-test="addCollection-createNewDatabase"]')
.check();
cy.wrap($body).find('input[data-test="addCollection-newDatabaseId"]').type(dbId); cy.wrap($body)
.find('input[data-test="addCollection-newDatabaseId"]')
.type(dbId);
cy.wrap($body).find('input[data-test="addCollectionPane-databaseSharedThroughput"]').uncheck(); cy.wrap($body)
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
.uncheck();
cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(collectionId); cy.wrap($body)
.find('input[data-test="addCollection-collectionId"]')
.type(collectionId);
cy.wrap($body).find('input[id="tab2"]').check({ force: true }); cy.wrap($body)
.find('input[id="tab2"]')
.check({ force: true });
cy.wrap($body).find('input[data-test="addCollection-partitionKeyValue"]').type(sharedKey); cy.wrap($body)
.find('input[data-test="addCollection-partitionKeyValue"]')
.type(sharedKey);
cy.wrap($body).find('input[data-test="databaseThroughputValue"]').should("have.value", "400"); cy.wrap($body)
.find('input[data-test="databaseThroughputValue"]')
.should("have.value", "400");
cy.wrap($body).find('input[data-test="addCollection-createCollection"]').click(); cy.wrap($body)
.find('input[data-test="addCollection-createCollection"]')
.click();
cy.wait(10000); cy.wait(10000);
@@ -109,7 +147,7 @@ context.skip("Mongo API Test", () => {
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`; const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then(($element) => { cy.get("iframe").then($element => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@@ -118,21 +156,38 @@ context.skip("Mongo API Test", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]'); cy.wrap($body)
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body).find('input[data-test="addCollection-createNewDatabase"]').check(); cy.wrap($body)
.find('input[data-test="addCollection-createNewDatabase"]')
.check();
cy.wrap($body).find('input[data-test="addCollection-newDatabaseId"]').type(dbId); cy.wrap($body)
.find('input[data-test="addCollection-newDatabaseId"]')
.type(dbId);
cy.wrap($body).find('input[data-test="addCollectionPane-databaseSharedThroughput"]').uncheck(); cy.wrap($body)
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
.uncheck();
cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(collectionId); cy.wrap($body)
.find('input[data-test="addCollection-collectionId"]')
.type(collectionId);
cy.wrap($body).find('input[id="tab1"]').check({ force: true }); cy.wrap($body)
.find('input[id="tab1"]')
.check({ force: true });
cy.wrap($body).find('input[data-test="databaseThroughputValue"]').should("have.value", "400"); cy.wrap($body)
.find('input[data-test="databaseThroughputValue"]')
.should("have.value", "400");
cy.wrap($body).find('input[data-test="addCollection-createCollection"]').click(); cy.wrap($body)
.find('input[data-test="addCollection-createCollection"]')
.click();
cy.wait(10000); cy.wait(10000);

View File

@@ -16,16 +16,15 @@ let crypt = require("crypto");
context("SQL API Test", () => { context("SQL API Test", () => {
beforeEach(() => { beforeEach(() => {
connectionString.loginUsingConnectionString(); connectionString.loginUsingConnectionString(connectionString.constants.sql);
}); });
it("Create a new container in SQL API", () => { it("Create a new container in SQL API", () => {
const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`; const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`;
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`; const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
connectionString.loginUsingConnectionString();
cy.get("iframe").then(($element) => { cy.get("iframe").then($element => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@@ -34,21 +33,38 @@ context("SQL API Test", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]'); cy.wrap($body)
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body).find('input[data-test="addCollection-createNewDatabase"]').check(); cy.wrap($body)
.find('input[data-test="addCollection-createNewDatabase"]')
.check();
cy.wrap($body).find('input[data-test="addCollectionPane-databaseSharedThroughput"]').check(); cy.wrap($body)
.find('input[data-test="addCollectionPane-databaseSharedThroughput"]')
.check();
cy.wrap($body).find('input[data-test="addCollection-newDatabaseId"]').type(dbId); cy.wrap($body)
.find('input[data-test="addCollection-newDatabaseId"]')
.type(dbId);
cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(collectionId); cy.wrap($body)
.find('input[data-test="addCollection-collectionId"]')
.type(collectionId);
cy.wrap($body).find('input[data-test="databaseThroughputValue"]').should("have.value", "400"); cy.wrap($body)
.find('input[data-test="databaseThroughputValue"]')
.should("have.value", "400");
cy.wrap($body).find('input[data-test="addCollection-partitionKeyValue"]').type(sharedKey); cy.wrap($body)
.find('input[data-test="addCollection-partitionKeyValue"]')
.type(sharedKey);
cy.wrap($body).find("#submitBtnAddCollection").click(); cy.wrap($body)
.find('input[data-test="addCollection-createCollection"]')
.click();
cy.wait(10000); cy.wait(10000);

View File

@@ -22,7 +22,7 @@ context("Table API Test", () => {
it("Create a new table in Table API", () => { it("Create a new table in Table API", () => {
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`; const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then(($element) => { cy.get("iframe").then($element => {
const $body = $element.contents().find("body"); const $body = $element.contents().find("body");
cy.wrap($body) cy.wrap($body)
.find('div[class="commandBarContainer"]') .find('div[class="commandBarContainer"]')
@@ -31,13 +31,22 @@ context("Table API Test", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wrap($body).find('div[class="contextual-pane-in"]').should("be.visible").find('span[id="containerTitle"]'); cy.wrap($body)
.find('div[class="contextual-pane-in"]')
.should("be.visible")
.find('span[id="containerTitle"]');
cy.wrap($body).find('input[data-test="addCollection-collectionId"]').type(collectionId); cy.wrap($body)
.find('input[data-test="addCollection-collectionId"]')
.type(collectionId);
cy.wrap($body).find('input[data-test="databaseThroughputValue"]').should("have.value", "400"); cy.wrap($body)
.find('input[data-test="databaseThroughputValue"]')
.should("have.value", "400");
cy.wrap($body).find('input[data-test="addCollection-createCollection"]').click(); cy.wrap($body)
.find('input[data-test="addCollection-createCollection"]')
.click();
cy.wait(10000); cy.wait(10000);

View File

@@ -29,7 +29,7 @@ context("Emulator - createDatabase", () => {
cy.get(".createNewDatabaseOrUseExisting") cy.get(".createNewDatabaseOrUseExisting")
.should("have.length", 2) .should("have.length", 2)
.and((input) => { .and(input => {
expect(input.get(0).textContent, "first item").contains("Create new"); expect(input.get(0).textContent, "first item").contains("Create new");
expect(input.get(1).textContent, "second item").contains("Use existing"); expect(input.get(1).textContent, "second item").contains("Use existing");
}); });

View File

@@ -38,15 +38,27 @@ context("Emulator - Create database -> container -> item", () => {
cy.get("[data-test=addCollection-partitionKeyValue]").type("/pk"); cy.get("[data-test=addCollection-partitionKeyValue]").type("/pk");
cy.get('input[name="createCollection"]').click(); cy.get('input[name="createCollection"]').click();
cy.get(".dataResourceTree").should("contain", databaseId); cy.get(".dataResourceTree").should("contain", databaseId);
cy.get(".dataResourceTree").contains(databaseId).click(); cy.get(".dataResourceTree")
.contains(databaseId)
.click();
cy.get(".dataResourceTree").should("contain", collectionId); cy.get(".dataResourceTree").should("contain", collectionId);
cy.get(".dataResourceTree").contains(collectionId).click(); cy.get(".dataResourceTree")
cy.get(".dataResourceTree").contains("Items").click(); .contains(collectionId)
cy.get(".dataResourceTree").contains("Items").click(); .click();
cy.get(".dataResourceTree")
.contains("Items")
.click();
cy.get(".dataResourceTree")
.contains("Items")
.click();
cy.wait(1000); // React rendering inside KO causes some weird async rendering that makes this test flaky without waiting cy.wait(1000); // React rendering inside KO causes some weird async rendering that makes this test flaky without waiting
cy.get(".commandBarContainer").contains("New Item").click(); cy.get(".commandBarContainer")
.contains("New Item")
.click();
cy.wait(1000); // React rendering inside KO causes some weird async rendering that makes this test flaky without waiting cy.wait(1000); // React rendering inside KO causes some weird async rendering that makes this test flaky without waiting
cy.get(".commandBarContainer").contains("Save").click(); cy.get(".commandBarContainer")
.contains("Save")
.click();
cy.wait(1000); // React rendering inside KO causes some weird async rendering that makes this test flaky without waiting cy.wait(1000); // React rendering inside KO causes some weird async rendering that makes this test flaky without waiting
cy.get(".documentsGridHeaderContainer").should("contain", "replace_with_new_document_id"); cy.get(".documentsGridHeaderContainer").should("contain", "replace_with_new_document_id");
}); });

View File

@@ -14,18 +14,25 @@ context("Emulator - deleteCollection", () => {
}); });
it("Delete a collection", () => { it("Delete a collection", () => {
cy.get(".databaseId").last().click(); cy.get(".databaseId")
.last()
.click();
cy.get(".collectionList") cy.get(".collectionList")
.last() .last()
.then(($id) => { .then($id => {
const collectionId = $id.text(); const collectionId = $id.text();
cy.get('span[data-test="collectionEllipsisMenu"]').should("exist"); cy.get('span[data-test="collectionEllipsisMenu"]').should("exist");
cy.get('span[data-test="collectionEllipsisMenu"]').invoke("show").last().click(); cy.get('span[data-test="collectionEllipsisMenu"]')
.invoke("show")
.last()
.click();
cy.get('div[data-test="collectionContextMenu"]').contains("Delete Container").click({ force: true }); cy.get('div[data-test="collectionContextMenu"]')
.contains("Delete Container")
.click({ force: true });
cy.get('input[data-test="confirmCollectionId"]').type(collectionId.trim()); cy.get('input[data-test="confirmCollectionId"]').type(collectionId.trim());

View File

@@ -22,10 +22,10 @@ context("Emulator - deleteDatabase", () => {
url: "https://localhost:8081/_explorer/authorization/post/dbs/", url: "https://localhost:8081/_explorer/authorization/post/dbs/",
headers: { headers: {
"x-ms-date": date, "x-ms-date": date,
authorization: "-", authorization: "-"
}, }
}) })
.then((response) => { .then(response => {
authToken = response.body.Token; // Getting auth token for collection creation authToken = response.body.Token; // Getting auth token for collection creation
return new Cypress.Promise((resolve, reject) => { return new Cypress.Promise((resolve, reject) => {
return resolve(); return resolve();
@@ -38,12 +38,12 @@ context("Emulator - deleteDatabase", () => {
headers: { headers: {
"x-ms-date": date, "x-ms-date": date,
authorization: authToken, authorization: authToken,
"x-ms-version": "2018-12-31", "x-ms-version": "2018-12-31"
}, },
body: { body: {
id: dbId, id: dbId
}, }
}).then((response) => { }).then(response => {
cy.log("Response", response); cy.log("Response", response);
db_rid = response.body._rid; db_rid = response.body._rid;
return new Cypress.Promise((resolve, reject) => { return new Cypress.Promise((resolve, reject) => {
@@ -59,14 +59,19 @@ context("Emulator - deleteDatabase", () => {
cy.get(".databaseId") cy.get(".databaseId")
.last() .last()
.then(($id) => { .then($id => {
const dbId = $id.text(); const dbId = $id.text();
cy.get('span[data-test="databaseEllipsisMenu"]').should("exist"); cy.get('span[data-test="databaseEllipsisMenu"]').should("exist");
cy.get('span[data-test="databaseEllipsisMenu"]').invoke("show").last().click(); cy.get('span[data-test="databaseEllipsisMenu"]')
.invoke("show")
.last()
.click();
cy.get('div[data-test="databaseContextMenu"]').contains("Delete Database").click({ force: true }); cy.get('div[data-test="databaseContextMenu"]')
.contains("Delete Database")
.click({ force: true });
cy.get('input[data-test="confirmDatabaseId"]').type(dbId.trim()); cy.get('input[data-test="confirmDatabaseId"]').type(dbId.trim());

View File

@@ -21,25 +21,29 @@ context("New Notebook smoke test", () => {
cy.contains("New Notebook").click(); cy.contains("New Notebook").click();
// Check tab name // Check tab name
cy.get("li.tabList .tabNavText").should(($span) => { cy.get("li.tabList .tabNavText").should($span => {
const text = $span.text(); const text = $span.text();
expect(text).to.match(/^Untitled.*\.ipynb$/); expect(text).to.match(/^Untitled.*\.ipynb$/);
}); });
// Wait for python3 | idle status // Wait for python3 | idle status
cy.get('[data-test="notebookStatusBar"] [data-test="kernelStatus"]', { timeout }).should(($p) => { cy.get('[data-test="notebookStatusBar"] [data-test="kernelStatus"]', { timeout }).should($p => {
const text = $p.text(); const text = $p.text();
expect(text).to.match(/^python3.*idle$/); expect(text).to.match(/^python3.*idle$/);
}); });
// Click on a cell // Click on a cell
cy.get(".cell-container").as("cellContainer").click(); cy.get(".cell-container")
.as("cellContainer")
.click();
// Type in some code // Type in some code
cy.get("@cellContainer").type("2+4"); cy.get("@cellContainer").type("2+4");
// Execute // Execute
cy.get('[data-test="Run"]').first().click(); cy.get('[data-test="Run"]')
.first()
.click();
// Verify results // Verify results
cy.get("@cellContainer").within(() => { cy.get("@cellContainer").within(() => {
@@ -47,29 +51,39 @@ context("New Notebook smoke test", () => {
}); });
// Restart kernel // Restart kernel
cy.get('[data-test="Run"] button').eq(-1).click(); cy.get('[data-test="Run"] button')
cy.get("li").contains("Restart Kernel").click(); .eq(-1)
.click();
cy.get("li")
.contains("Restart Kernel")
.click();
// Wait for python3 | restarting status // Wait for python3 | restarting status
cy.get('[data-test="notebookStatusBar"] [data-test="kernelStatus"]', { timeout }).should(($p) => { cy.get('[data-test="notebookStatusBar"] [data-test="kernelStatus"]', { timeout }).should($p => {
const text = $p.text(); const text = $p.text();
expect(text).to.match(/^python3.*restarting$/); expect(text).to.match(/^python3.*restarting$/);
}); });
// Wait for python3 | idle status // Wait for python3 | idle status
cy.get('[data-test="notebookStatusBar"] [data-test="kernelStatus"]', { timeout }).should(($p) => { cy.get('[data-test="notebookStatusBar"] [data-test="kernelStatus"]', { timeout }).should($p => {
const text = $p.text(); const text = $p.text();
expect(text).to.match(/^python3.*idle$/); expect(text).to.match(/^python3.*idle$/);
}); });
// Click on a cell // Click on a cell
cy.get(".cell-container").as("cellContainer").find(".input").as("codeInput").click(); cy.get(".cell-container")
.as("cellContainer")
.find(".input")
.as("codeInput")
.click();
// Type in some code // Type in some code
cy.get("@codeInput").type("{backspace}{backspace}{backspace}4+5"); cy.get("@codeInput").type("{backspace}{backspace}{backspace}4+5");
// Execute // Execute
cy.get('[data-test="Run"]').first().click(); cy.get('[data-test="Run"]')
.first()
.click();
// Verify results // Verify results
cy.get("@cellContainer").within(() => { cy.get("@cellContainer").within(() => {

View File

@@ -11,11 +11,15 @@ context("Resource tree notebook file manipulation", () => {
}; };
const clickContextMenuAndSelectOption = (nodeLabel, option) => { const clickContextMenuAndSelectOption = (nodeLabel, option) => {
cy.get(`.treeNodeHeader[data-test="${nodeLabel}"]`).find("button.treeMenuEllipsis").click(); cy.get(`.treeNodeHeader[data-test="${nodeLabel}"]`)
cy.get('[data-test="treeComponentMenuItemContainer"]').contains(option).click(); .find("button.treeMenuEllipsis")
.click();
cy.get('[data-test="treeComponentMenuItemContainer"]')
.contains(option)
.click();
}; };
const createFolder = (folder) => { const createFolder = folder => {
clickContextMenuAndSelectOption("My Notebooks/", "New Directory"); clickContextMenuAndSelectOption("My Notebooks/", "New Directory");
cy.get("#stringInputPane").within(() => { cy.get("#stringInputPane").within(() => {
@@ -24,9 +28,11 @@ context("Resource tree notebook file manipulation", () => {
}); });
}; };
const deleteItem = (nodeName) => { const deleteItem = nodeName => {
clickContextMenuAndSelectOption(`${nodeName}`, "Delete"); clickContextMenuAndSelectOption(`${nodeName}`, "Delete");
cy.get(".ms-Dialog-main").contains("Delete").click(); cy.get(".ms-Dialog-main")
.contains("Delete")
.click();
}; };
beforeEach(() => { beforeEach(() => {
@@ -50,7 +56,9 @@ context("Resource tree notebook file manipulation", () => {
// Rename // Rename
clickContextMenuAndSelectOption(`${folder}/`, "Rename"); clickContextMenuAndSelectOption(`${folder}/`, "Rename");
cy.get("#stringInputPane").within(() => { cy.get("#stringInputPane").within(() => {
cy.get('input[name="collectionIdConfirmation"]').clear().type(renamedFolder); cy.get('input[name="collectionIdConfirmation"]')
.clear()
.type(renamedFolder);
cy.get("form").submit(); cy.get("form").submit();
}); });
cy.get(`.treeNodeHeader[data-test="${renamedFolder}/"]`).should("exist"); cy.get(`.treeNodeHeader[data-test="${renamedFolder}/"]`).should("exist");
@@ -67,12 +75,16 @@ context("Resource tree notebook file manipulation", () => {
clickContextMenuAndSelectOption(`${folder}/`, "New Notebook"); clickContextMenuAndSelectOption(`${folder}/`, "New Notebook");
// Verify tab is open // Verify tab is open
cy.get(".tabList").contains(newNotebookName).should("exist"); cy.get(".tabList")
.contains(newNotebookName)
.should("exist");
// Close tab // Close tab
cy.get(`.tabList[title="notebooks/${folder}/${newNotebookName}"]`).find(".cancelButton").click(); cy.get(`.tabList[title="notebooks/${folder}/${newNotebookName}"]`)
.find(".cancelButton")
.click();
// When running from command line, closing the tab is too fast // When running from command line, closing the tab is too fast
cy.get("body").then(($body) => { cy.get("body").then($body => {
if ($body.find(".ms-Dialog-main").length) { if ($body.find(".ms-Dialog-main").length) {
// For some reason, this does not work // For some reason, this does not work
// cy.get(".ms-Dialog-main").contains("Close").click(); // cy.get(".ms-Dialog-main").contains("Close").click();
@@ -88,10 +100,14 @@ context("Resource tree notebook file manipulation", () => {
cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`) cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`)
.find("button.treeMenuEllipsis") .find("button.treeMenuEllipsis")
.click(); .click();
cy.get('[data-test="treeComponentMenuItemContainer"]').contains("Delete").click(); cy.get('[data-test="treeComponentMenuItemContainer"]')
.contains("Delete")
.click();
// Confirm // Confirm
cy.get(".ms-Dialog-main").contains("Delete").click(); cy.get(".ms-Dialog-main")
.contains("Delete")
.click();
cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`).should("not.exist"); cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`).should("not.exist");
deleteItem(`${folder}/`); deleteItem(`${folder}/`);
@@ -105,8 +121,10 @@ context("Resource tree notebook file manipulation", () => {
clickContextMenuAndSelectOption(`${folder}/`, "New Notebook"); clickContextMenuAndSelectOption(`${folder}/`, "New Notebook");
// Close tab // Close tab
cy.get(`.tabList[title="notebooks/${folder}/${newNotebookName}"]`).find(".cancelButton").click(); cy.get(`.tabList[title="notebooks/${folder}/${newNotebookName}"]`)
cy.get("body").then(($body) => { .find(".cancelButton")
.click();
cy.get("body").then($body => {
if ($body.find(".ms-Dialog-main").length) { if ($body.find(".ms-Dialog-main").length) {
// For some reason, this does not work // For some reason, this does not work
// cy.get(".ms-Dialog-main").contains("Close").click(); // cy.get(".ms-Dialog-main").contains("Close").click();
@@ -122,10 +140,14 @@ context("Resource tree notebook file manipulation", () => {
cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`) cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`)
.find("button.treeMenuEllipsis") .find("button.treeMenuEllipsis")
.click(); .click();
cy.get('[data-test="treeComponentMenuItemContainer"]').contains("Rename").click(); cy.get('[data-test="treeComponentMenuItemContainer"]')
.contains("Rename")
.click();
cy.get("#stringInputPane").within(() => { cy.get("#stringInputPane").within(() => {
cy.get('input[name="collectionIdConfirmation"]').clear().type(renamedNotebookName); cy.get('input[name="collectionIdConfirmation"]')
.clear()
.type(renamedNotebookName);
cy.get("form").submit(); cy.get("form").submit();
}); });
cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`).should("not.exist"); cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${newNotebookName}"]`).should("not.exist");
@@ -135,10 +157,14 @@ context("Resource tree notebook file manipulation", () => {
cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${renamedNotebookName}"]`) cy.get(`.nodeChildren[data-test="${folder}/"] .treeNodeHeader[data-test="${renamedNotebookName}"]`)
.find("button.treeMenuEllipsis") .find("button.treeMenuEllipsis")
.click(); .click();
cy.get('[data-test="treeComponentMenuItemContainer"]').contains("Delete").click(); cy.get('[data-test="treeComponentMenuItemContainer"]')
.contains("Delete")
.click();
// Confirm // Confirm
cy.get(".ms-Dialog-main").contains("Delete").click(); cy.get(".ms-Dialog-main")
.contains("Delete")
.click();
// Give it time to settle // Give it time to settle
cy.wait(1000); cy.wait(1000);
deleteItem(`${folder}/`); deleteItem(`${folder}/`);

View File

@@ -273,12 +273,77 @@
"any-observable": "^0.3.0" "any-observable": "^0.3.0"
} }
}, },
"@types/sinonjs__fake-timers": { "@types/blob-util": {
"version": "6.0.1", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/blob-util/-/blob-util-1.3.3.tgz",
"integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==", "integrity": "sha512-4ahcL/QDnpjWA2Qs16ZMQif7HjGP2cw3AGjHabybjw7Vm1EKu+cfQN1D78BaZbS1WJNa1opSMF5HNMztx7lR0w==",
"dev": true "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": { "@types/sizzle": {
"version": "2.3.2", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz",
@@ -762,15 +827,24 @@
} }
}, },
"cypress": { "cypress": {
"version": "4.8.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-4.8.0.tgz", "resolved": "https://registry.npmjs.org/cypress/-/cypress-4.5.0.tgz",
"integrity": "sha512-Lsff8lF8pq6k/ioNua783tCsxGSLp6gqGXecdIfqCkqjYiOA53XKuEf1CaQJLUVs1dHSf49eDUp/sb620oJjVQ==", "integrity": "sha512-2A4g5FW5d2fHzq8HKUGAMVTnW6P8nlWYQALiCoGN4bqBLvgwhYM/oG9oKc2CS6LnvgHFiKivKzpm9sfk3uU3zQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@cypress/listr-verbose-renderer": "0.4.1", "@cypress/listr-verbose-renderer": "0.4.1",
"@cypress/request": "2.88.5", "@cypress/request": "2.88.5",
"@cypress/xvfb": "1.2.4", "@cypress/xvfb": "1.2.4",
"@types/sinonjs__fake-timers": "6.0.1", "@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/sizzle": "2.3.2", "@types/sizzle": "2.3.2",
"arch": "2.1.1", "arch": "2.1.1",
"bluebird": "3.7.2", "bluebird": "3.7.2",
@@ -804,6 +878,14 @@
"untildify": "4.0.0", "untildify": "4.0.0",
"url": "0.11.0", "url": "0.11.0",
"yauzl": "2.10.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": { "dashdash": {
@@ -999,6 +1081,12 @@
"ms": "2.0.0" "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": { "mkdirp": {
"version": "0.5.5", "version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
@@ -1771,12 +1859,6 @@
"brace-expansion": "^1.1.7" "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": { "mkdirp": {
"version": "0.5.1", "version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",

View File

@@ -5,13 +5,11 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "cypress run", "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: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" "test:debug": "cypress open"
}, },
"devDependencies": { "devDependencies": {
"cypress": "^4.8.0", "cypress": "^4.5.0",
"mocha": "^7.0.1", "mocha": "^7.0.1",
"mochawesome": "^4.1.0", "mochawesome": "^4.1.0",
"mochawesome-merge": "^4.0.1", "mochawesome-merge": "^4.0.1",

View File

@@ -1,41 +1,56 @@
module.exports = { module.exports = {
loginUsingConnectionString: function() { loginUsingConnectionString: function (api) {
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");
cy.get("iframe").then($element => { const prodUrl = "https://cosmos.azure.com/";
const $body = $element.contents().find("body"); const timeout = 15000;
cy.wrap($body) cy.visit(prodUrl);
.find("#connectExplorer") cy.get('iframe[id="explorerMenu"]').should("be.visible");
.should("exist")
.find("div[class='connectExplorer']")
.should("exist")
.find("p[class='welcomeText']")
.should("exist");
cy.wrap($body.find("div > p.switchConnectTypeText")) cy.get("iframe").then($element => {
.should("exist") const $body = $element.contents().find("body");
.last()
.click({ force: true });
const secret = Cypress.env("CONNECTION_STRING"); cy.wrap($body)
.find("#connectExplorer")
.should("exist")
.find("div[class='connectExplorer']")
.should("exist")
.find("p[class='welcomeText']")
.should("exist");
cy.wrap($body) cy.wrap($body.find("div > p.switchConnectTypeText"))
.find("input[class='inputToken']") .should("exist")
.should("exist") .last()
.type(secret, { .click({ force: true });
force: true
});
cy.wrap($body.find("input[value='Connect']"), { timeout }) const secret = Cypress.env('connectionString')[api];
.first()
.click({ force: true });
cy.wait(15000); 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");
});
},
constants:{
sql: "sql",
mongo: "mongo",
table: "table",
graph: "graph",
cassandra: "cassandra"
}
}

View File

@@ -1,9 +0,0 @@
const isCI = require("is-ci");
module.exports = {
launch: {
headless: isCI,
slowMo: isCI ? null : 20,
defaultViewport: null,
},
};

View File

@@ -1,5 +0,0 @@
module.exports = {
preset: "jest-puppeteer",
testMatch: ["<rootDir>/test/**/*.spec.[jt]s?(x)"],
setupFiles: ["dotenv/config"],
};

View File

@@ -42,8 +42,8 @@ module.exports = {
branches: 18, branches: 18,
functions: 22, functions: 22,
lines: 28, lines: 28,
statements: 27, statements: 27
}, }
}, },
// Make calling deprecated APIs throw helpful error messages // Make calling deprecated APIs throw helpful error messages
@@ -76,7 +76,7 @@ module.exports = {
"office-ui-fabric-react/lib/(.*)$": "office-ui-fabric-react/lib-commonjs/$1", // https://github.com/OfficeDev/office-ui-fabric-react/wiki/Fabric-6-Release-Notes "office-ui-fabric-react/lib/(.*)$": "office-ui-fabric-react/lib-commonjs/$1", // https://github.com/OfficeDev/office-ui-fabric-react/wiki/Fabric-6-Release-Notes
"^dnd-core$": "dnd-core/dist/cjs", "^dnd-core$": "dnd-core/dist/cjs",
"^react-dnd$": "react-dnd/dist/cjs", "^react-dnd$": "react-dnd/dist/cjs",
"^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs", "^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs"
}, },
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
@@ -150,7 +150,7 @@ module.exports = {
// testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?|ts?)$", // testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?|ts?)$",
// This option allows the use of a custom results processor // This option allows the use of a custom results processor
// testResultsProcessor: "./trxProcessor.js", testResultsProcessor: "./trxProcessor.js",
// This option allows use of a custom test runner // This option allows use of a custom test runner
// testRunner: "jasmine2", // testRunner: "jasmine2",
@@ -164,11 +164,11 @@ module.exports = {
// A map from regular expressions to paths to transformers // A map from regular expressions to paths to transformers
transform: { transform: {
"^.+\\.html?$": "html-loader-jest", "^.+\\.html?$": "html-loader-jest",
"^.+\\.[t|j]sx?$": "babel-jest", "^.+\\.[t|j]sx?$": "babel-jest"
}, },
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
transformIgnorePatterns: ["/node_modules/", "/externals/"], transformIgnorePatterns: ["/node_modules/", "/externals/"]
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined, // unmockedModulePathPatterns: undefined,

View File

@@ -54,8 +54,6 @@
@SelectionColor: #3074B0; @SelectionColor: #3074B0;
@FocusColor: #00bcf2;
/****************************************************************************** /******************************************************************************
METRICS METRICS
/******************************************************************************/ /******************************************************************************/
@@ -200,7 +198,7 @@
} }
.focus() { .focus() {
outline: 1px dashed @FocusColor; outline: 1px dashed @AccentMedium;
} }
/************************************************************************************************ /************************************************************************************************

View File

@@ -14,10 +14,6 @@ body {
font-family: @DataExplorerFont; font-family: @DataExplorerFont;
font-size: 12px; font-size: 12px;
height: 100%; height: 100%;
:focus {
.focus()
}
} }
.float-right { .float-right {
@@ -574,12 +570,6 @@ body {
} }
} }
.fileImportButton {
height: 24px;
border: @ButtonBorderWidth solid transparent;
vertical-align: top;
}
.fileUploadSummaryContainer { .fileUploadSummaryContainer {
margin-top: 40px; margin-top: 40px;
@@ -1026,18 +1016,6 @@ menuQuickStart {
background: #262626; background: #262626;
} }
.panelContent {
display: flex;
flex-direction: column;
flex: 1;
}
.panelContentWrapper {
display: flex;
flex-direction: column;
height: 100%;
}
.contextual-pane { .contextual-pane {
top: 0px; top: 0px;
right: 0 !important; right: 0 !important;
@@ -1254,25 +1232,23 @@ menuQuickStart {
padding: 2px 30px; padding: 2px 30px;
cursor: pointer; cursor: pointer;
font-size: 12px; font-size: 12px;
&:active {
border-color: #0072c6;
background-color: #0072c6;
}
} }
.leftpanel-okbut .genericPaneSubmitBtn { .btncreatecoll1:hover {
border: 1px solid @AccentMediumHigh; background: @AccentMediumHigh;
background-color: @AccentMediumHigh;
color: #fff; color: #fff;
border-color: @AccentMediumHigh;
cursor: pointer; cursor: pointer;
font-size: 12px; font-size: 12px;
height: 24px; }
&:active { .btncreatecoll1:active {
border-color: #0072c6; border: 1px solid #0072c6;
background-color: #0072c6; background-color: #0072c6;
} color: white;
padding: 2px 30px;
cursor: pointer;
font-size: 12px;
} }
.btncreatecoll1-off { .btncreatecoll1-off {
@@ -1385,15 +1361,6 @@ p {
color: #000; color: #000;
} }
.headerline .closePaneBtn {
float: right;
cursor: pointer;
width: 16px;
height: 100%;
margin-right: 4px;
color: #000;
}
.closeImg { .closeImg {
float: right; float: right;
cursor: pointer; cursor: pointer;
@@ -1743,13 +1710,6 @@ input::-webkit-calendar-picker-indicator {
margin: (2 * @MediumSpace) 0px; margin: (2 * @MediumSpace) 0px;
} }
.contextual-pane .panelMainContent {
padding-left: 34px;
padding-right: 34px;
color: @BaseDark;
margin: (2 * @MediumSpace) 0px;
}
.contextual-pane .paneFooter { .contextual-pane .paneFooter {
width: 100%; width: 100%;
height: 60px; height: 60px;

9838
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
"description": "Cosmos Explorer", "description": "Cosmos Explorer",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@azure/cosmos": "3.7.4", "@azure/cosmos": "3.7.0",
"@azure/cosmos-language-service": "0.0.4", "@azure/cosmos-language-service": "0.0.4",
"@jupyterlab/services": "4.2.0", "@jupyterlab/services": "4.2.0",
"@jupyterlab/terminal": "1.2.1", "@jupyterlab/terminal": "1.2.1",
@@ -37,25 +37,22 @@
"@uifabric/react-cards": "0.109.53", "@uifabric/react-cards": "0.109.53",
"@uifabric/styling": "7.11.2", "@uifabric/styling": "7.11.2",
"abort-controller": "3.0.0", "abort-controller": "3.0.0",
"applicationinsights": "1.8.0",
"babel-polyfill": "6.26.0", "babel-polyfill": "6.26.0",
"bootstrap": "3.3.7", "bootstrap": "3.3.7",
"canvas": "2.6.0", "canvas": "2.6.0",
"clean-webpack-plugin": "0.1.19", "clean-webpack-plugin": "0.1.19",
"copy-webpack-plugin": "6.0.2", "copy-webpack-plugin": "4.5.4",
"crossroads": "0.12.2", "crossroads": "0.12.2",
"css-element-queries": "1.1.1", "css-element-queries": "1.1.1",
"datatables.net-colreorder-dt": "1.5.1", "datatables.net-colreorder-dt": "1.5.1",
"datatables.net-dt": "1.10.19", "datatables.net-dt": "1.10.19",
"date-fns": "1.29.0", "date-fns": "1.29.0",
"dayjs": "1.8.19", "dayjs": "1.8.19",
"dotenv": "8.2.0",
"es6-object-assign": "1.1.0", "es6-object-assign": "1.1.0",
"es6-symbol": "3.1.3", "es6-symbol": "3.1.3",
"eslint-plugin-jest": "23.13.2", "eslint-plugin-jest": "23.8.2",
"hasher": "1.2.0", "hasher": "1.2.0",
"immutable": "4.0.0-rc.12", "immutable": "4.0.0-rc.12",
"is-ci": "2.0.0",
"jquery": "3.4.0", "jquery": "3.4.0",
"jquery-typeahead": "2.10.6", "jquery-typeahead": "2.10.6",
"jquery-ui-dist": "1.12.1", "jquery-ui-dist": "1.12.1",
@@ -113,8 +110,8 @@
"@types/text-encoding": "0.0.33", "@types/text-encoding": "0.0.33",
"@types/underscore": "1.7.36", "@types/underscore": "1.7.36",
"@types/webfontloader": "1.6.29", "@types/webfontloader": "1.6.29",
"@typescript-eslint/eslint-plugin": "3.2.0", "@typescript-eslint/eslint-plugin": "2.25.0",
"@typescript-eslint/parser": "3.2.0", "@typescript-eslint/parser": "2.25.0",
"adal-angular": "1.0.15", "adal-angular": "1.0.15",
"babel-jest": "24.9.0", "babel-jest": "24.9.0",
"babel-loader": "8.1.0", "babel-loader": "8.1.0",
@@ -126,10 +123,9 @@
"enzyme": "3.10.0", "enzyme": "3.10.0",
"enzyme-adapter-react-16": "1.15.1", "enzyme-adapter-react-16": "1.15.1",
"enzyme-to-json": "3.4.3", "enzyme-to-json": "3.4.3",
"eslint": "7.3.1", "eslint": "6.8.0",
"eslint-cli": "1.1.1", "eslint-cli": "1.1.1",
"eslint-plugin-no-null": "1.0.2", "eslint-plugin-react": "7.19.0",
"eslint-plugin-react": "7.20.0",
"expose-loader": "0.7.5", "expose-loader": "0.7.5",
"file-loader": "2.0.0", "file-loader": "2.0.0",
"fs-extra": "7.0.0", "fs-extra": "7.0.0",
@@ -137,31 +133,29 @@
"html-loader-jest": "0.2.1", "html-loader-jest": "0.2.1",
"html-webpack-plugin": "3.2.0", "html-webpack-plugin": "3.2.0",
"inline-css": "2.2.5", "inline-css": "2.2.5",
"jest": "25.5.4", "jest": "24.9.0",
"jest-canvas-mock": "2.1.0", "jest-canvas-mock": "2.1.0",
"jest-puppeteer": "4.4.0",
"jest-trx-results-processor": "0.0.7", "jest-trx-results-processor": "0.0.7",
"less": "3.8.1", "less": "3.8.1",
"less-loader": "4.1.0", "less-loader": "4.1.0",
"less-vars-loader": "1.1.0", "less-vars-loader": "1.1.0",
"mini-css-extract-plugin": "0.4.3", "mini-css-extract-plugin": "0.4.3",
"monaco-editor-webpack-plugin": "1.7.0", "monaco-editor-webpack-plugin": "1.7.0",
"prettier": "2.0.5", "prettier": "1.19.1",
"puppeteer": "4.0.0",
"raw-loader": "0.5.1", "raw-loader": "0.5.1",
"rimraf": "3.0.0", "rimraf": "3.0.0",
"sinon": "3.2.1", "sinon": "3.2.1",
"style-loader": "0.23.0", "style-loader": "0.23.0",
"terser-webpack-plugin": "3.0.5", "terser-webpack-plugin": "2.3.5",
"ts-loader": "6.2.2", "ts-loader": "6.2.2",
"tslint": "5.11.0", "tslint": "5.11.0",
"tslint-microsoft-contrib": "6.0.0", "tslint-microsoft-contrib": "6.0.0",
"typescript": "3.8.3", "typescript": "3.8.3",
"url-loader": "1.1.1", "url-loader": "1.1.1",
"webpack": "4.43.0", "webpack": "4.41.2",
"webpack-bundle-analyzer": "3.6.1", "webpack-bundle-analyzer": "3.6.1",
"webpack-cli": "3.3.10", "webpack-cli": "3.3.10",
"webpack-dev-server": "3.11.0", "webpack-dev-server": "3.9.0",
"worker-loader": "2.0.0" "worker-loader": "2.0.0"
}, },
"scripts": { "scripts": {
@@ -174,8 +168,8 @@
"pack:fast": "node --max_old_space_size=10196 ./node_modules/webpack/bin/webpack.js --mode development --progress", "pack:fast": "node --max_old_space_size=10196 ./node_modules/webpack/bin/webpack.js --mode development --progress",
"copyToConsumers": "node copyToConsumers", "copyToConsumers": "node copyToConsumers",
"test": "rimraf coverage && jest", "test": "rimraf coverage && jest",
"test:e2e": "jest -c ./jest.config.e2e.js --detectOpenHandles",
"watch": "npm run start", "watch": "npm run start",
"integrationTest": "runIntegrationTests.cmd",
"build:ase": "gulp build:ase", "build:ase": "gulp build:ase",
"compile": "tsc", "compile": "tsc",
"compile:contracts": "tsc -p ./tsconfig.contracts.json", "compile:contracts": "tsc -p ./tsconfig.contracts.json",

24
runIntegrationTests.cmd Normal file
View File

@@ -0,0 +1,24 @@
@echo off
@for /f "delims=" %%P in ('npm prefix -g') do set "NPM_PREFIX=%%P"
@echo npm prefix = %NPM_PREFIX%
@echo Compiling TypeScript Test Sources ...
call %NPM_PREFIX%\tsc -p ./test
if %errorlevel% neq 0 goto end
copy .\test\Integration\TestRunner.html .\test\out\test\Integration /y >nul 2>&1
@echo Copying files for test simulation against Emulator ...
rmdir "%ProgramFiles%\Azure Cosmos DB Emulator\Packages\DataExplorer\test" >nul 2>&1
mkdir "%ProgramFiles%\Azure Cosmos DB Emulator\Packages\DataExplorer\test" >nul 2>&1
xcopy .\node_modules\jasmine-core\lib .\test\out\lib /s /c /i /r /y >nul 2>&1
xcopy .\node_modules\jasmine-core\images .\test\out\lib\images /s /c /i /r /y >nul 2>&1
xcopy .\test\out "%ProgramFiles%\Azure Cosmos DB Emulator\Packages\DataExplorer\test" /s /c /i /r /y >nul 2>&1
@echo Initiating test runner ...
start https://localhost:8081/_explorer/test/test/Integration/TestRunner.html
@echo Done!
:end
@echo on

View File

@@ -2,5 +2,5 @@ export enum AuthType {
AAD = "aad", AAD = "aad",
EncryptedToken = "encryptedtoken", EncryptedToken = "encryptedtoken",
MasterKey = "masterkey", MasterKey = "masterkey",
ResourceToken = "resourcetoken", ResourceToken = "resourcetoken"
} }

View File

@@ -18,7 +18,7 @@ export class BindingHandlersRegisterer {
) { ) {
const value = ko.unwrap(wrappedValueAccessor()); const value = ko.unwrap(wrappedValueAccessor());
bindingContext.$data.isTemplateReady(value); bindingContext.$data.isTemplateReady(value);
}, }
} as ko.BindingHandler; } as ko.BindingHandler;
ReactBindingHandler.Registerer.register(); ReactBindingHandler.Registerer.register();

View File

@@ -42,7 +42,7 @@ export class Registerer {
// Initial rendering at mount point // Initial rendering at mount point
ReactDOM.render(adapter.renderComponent(), element); ReactDOM.render(adapter.renderComponent(), element);
}, }
} as ko.BindingHandler; } as ko.BindingHandler;
} }
} }

View File

@@ -40,7 +40,7 @@ export class ArrayHashMap<T> {
public forEach(key: string, iteratorFct: (value: T) => void) { public forEach(key: string, iteratorFct: (value: T) => void) {
const values = this.store.get(key); const values = this.store.get(key);
if (values) { if (values) {
values.forEach((value) => iteratorFct(value)); values.forEach(value => iteratorFct(value));
} }
} }

View File

@@ -104,25 +104,29 @@ export class CapabilityNames {
} }
export class Features { export class Features {
public static readonly graphs = "graphs";
public static readonly cosmosdb = "cosmosdb"; public static readonly cosmosdb = "cosmosdb";
public static readonly enableChangeFeedPolicy = "enablechangefeedpolicy"; public static readonly enableChangeFeedPolicy = "enablechangefeedpolicy";
public static readonly enableRupm = "enablerupm"; public static readonly enableRupm = "enablerupm";
public static readonly cacheOptimizations = "dataexplorercacheoptimizations";
public static readonly executeSproc = "dataexplorerexecutesproc"; public static readonly executeSproc = "dataexplorerexecutesproc";
public static readonly hostedDataExplorer = "hosteddataexplorerenabled"; public static readonly hostedDataExplorer = "hosteddataexplorerenabled";
public static readonly enableTtl = "enablettl"; public static readonly enableTtl = "enablettl";
public static readonly enableNotebooks = "enablenotebooks"; public static readonly enableNotebooks = "enablenotebooks";
public static readonly enableGallery = "enablegallery"; public static readonly enableGallery = "enablegallery";
public static readonly enableGalleryPublish = "enablegallerypublish";
public static readonly enableSpark = "enablespark"; public static readonly enableSpark = "enablespark";
public static readonly livyEndpoint = "livyendpoint"; 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 notebookServerUrl = "notebookserverurl";
public static readonly notebookServerToken = "notebookservertoken"; public static readonly notebookServerToken = "notebookservertoken";
public static readonly notebookBasePath = "notebookbasepath"; public static readonly notebookBasePath = "notebookbasepath";
public static readonly enableLegacyResourceTree = "enablelegacyresourcetree";
public static readonly canExceedMaximumValue = "canexceedmaximumvalue"; public static readonly canExceedMaximumValue = "canexceedmaximumvalue";
public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput"; public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput";
public static readonly enableAutoPilotV2 = "enableautopilotv2"; public static readonly enableAutoPilotV2 = "enableautopilotv2";
public static readonly ttl90Days = "ttl90days"; public static readonly ttl90Days = "ttl90days";
public static readonly enableRightPanelV2 = "enablerightpanelv2";
} }
export class AfecFeatures { export class AfecFeatures {
@@ -140,7 +144,7 @@ export class Spark {
"Cosmos.Spark.D8s": "D8s / 8 cores / 32GB RAM", "Cosmos.Spark.D8s": "D8s / 8 cores / 32GB RAM",
"Cosmos.Spark.D16s": "D16s / 16 cores / 64GB RAM", "Cosmos.Spark.D16s": "D16s / 16 cores / 64GB RAM",
"Cosmos.Spark.D32s": "D32s / 32 cores / 128GB RAM", "Cosmos.Spark.D32s": "D32s / 32 cores / 128GB RAM",
"Cosmos.Spark.D64s": "D64s / 64 cores / 256GB RAM", "Cosmos.Spark.D64s": "D64s / 64 cores / 256GB RAM"
}); });
} }
@@ -155,7 +159,7 @@ export class MongoDBAccounts {
export enum MongoBackendEndpointType { export enum MongoBackendEndpointType {
local, local,
remote, remote
} }
export class MongoBackend { export class MongoBackend {
@@ -180,14 +184,14 @@ export class MongoBackend {
koreasouth: MongoBackend.southEastAsiaEndpoint, koreasouth: MongoBackend.southEastAsiaEndpoint,
southeastasia: MongoBackend.southEastAsiaEndpoint, southeastasia: MongoBackend.southEastAsiaEndpoint,
southindia: MongoBackend.southEastAsiaEndpoint, southindia: MongoBackend.southEastAsiaEndpoint,
westindia: MongoBackend.southEastAsiaEndpoint, westindia: MongoBackend.southEastAsiaEndpoint
}; };
public static endpointsByEnvironment: any = { public static endpointsByEnvironment: any = {
default: MongoBackendEndpointType.local, default: MongoBackendEndpointType.local,
localhost: MongoBackendEndpointType.local, localhost: MongoBackendEndpointType.local,
prod1: MongoBackendEndpointType.remote, prod1: MongoBackendEndpointType.remote,
prod2: MongoBackendEndpointType.remote, prod2: MongoBackendEndpointType.remote
}; };
} }
@@ -238,7 +242,7 @@ export class CassandraBackend {
chinaeast: CassandraBackend.mc_default, chinaeast: CassandraBackend.mc_default,
chinaeast2: CassandraBackend.mc_default, chinaeast2: CassandraBackend.mc_default,
chinanorth: CassandraBackend.mc_default, chinanorth: CassandraBackend.mc_default,
chinanorth2: CassandraBackend.mc_default, chinanorth2: CassandraBackend.mc_default
}; };
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete"; public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
@@ -368,7 +372,7 @@ export class HttpStatusCodes {
HttpStatusCodes.InternalServerError, // TODO: Handle all 500s on Portal backend and remove from retries list HttpStatusCodes.InternalServerError, // TODO: Handle all 500s on Portal backend and remove from retries list
HttpStatusCodes.BadGateway, HttpStatusCodes.BadGateway,
HttpStatusCodes.ServiceUnavailable, HttpStatusCodes.ServiceUnavailable,
HttpStatusCodes.GatewayTimeout, HttpStatusCodes.GatewayTimeout
]; ];
} }
@@ -422,7 +426,10 @@ export class HashRoutePrefixes {
public static docsWithIds(databaseId: string, collectionId: string, docId: string) { public static docsWithIds(databaseId: string, collectionId: string, docId: string) {
const transformedDatabasePrefix: string = this.docs.replace("{db_id}", databaseId); const transformedDatabasePrefix: string = this.docs.replace("{db_id}", databaseId);
return transformedDatabasePrefix.replace("{coll_id}", collectionId).replace("{doc_id}", docId).replace("/", ""); // strip the first slash since hasher adds it return transformedDatabasePrefix
.replace("{coll_id}", collectionId)
.replace("{doc_id}", docId)
.replace("/", ""); // strip the first slash since hasher adds it
} }
} }
@@ -457,7 +464,7 @@ export class OfferVersions {
export enum ConflictOperationType { export enum ConflictOperationType {
Replace = "replace", Replace = "replace",
Create = "create", Create = "create",
Delete = "delete", Delete = "delete"
} }
export class AutoPilot { export class AutoPilot {
@@ -470,28 +477,28 @@ export class AutoPilot {
[AutopilotTier.Tier1]: "Tier 1", [AutopilotTier.Tier1]: "Tier 1",
[AutopilotTier.Tier2]: "Tier 2", [AutopilotTier.Tier2]: "Tier 2",
[AutopilotTier.Tier3]: "Tier 3", [AutopilotTier.Tier3]: "Tier 3",
[AutopilotTier.Tier4]: "Tier 4", [AutopilotTier.Tier4]: "Tier 4"
}; };
public static tierMaxRus = { public static tierMaxRus = {
[AutopilotTier.Tier1]: 2000, [AutopilotTier.Tier1]: 2000,
[AutopilotTier.Tier2]: 20000, [AutopilotTier.Tier2]: 20000,
[AutopilotTier.Tier3]: 100000, [AutopilotTier.Tier3]: 100000,
[AutopilotTier.Tier4]: 500000, [AutopilotTier.Tier4]: 500000
}; };
public static tierMinRus = { public static tierMinRus = {
[AutopilotTier.Tier1]: 0, [AutopilotTier.Tier1]: 0,
[AutopilotTier.Tier2]: 0, [AutopilotTier.Tier2]: 0,
[AutopilotTier.Tier3]: 0, [AutopilotTier.Tier3]: 0,
[AutopilotTier.Tier4]: 0, [AutopilotTier.Tier4]: 0
}; };
public static tierStorageInGB = { public static tierStorageInGB = {
[AutopilotTier.Tier1]: 50, [AutopilotTier.Tier1]: 50,
[AutopilotTier.Tier2]: 200, [AutopilotTier.Tier2]: 200,
[AutopilotTier.Tier3]: 1000, [AutopilotTier.Tier3]: 1000,
[AutopilotTier.Tier4]: 5000, [AutopilotTier.Tier4]: 5000
}; };
} }
@@ -505,7 +512,7 @@ export class DataExplorerFeatures {
} }
export const DataExplorerFeaturesVersions: any = { export const DataExplorerFeaturesVersions: any = {
OfferCache: DataExplorerVersions.v_1_0_1, OfferCache: DataExplorerVersions.v_1_0_1
}; };
export const EmulatorMasterKey = export const EmulatorMasterKey =

View File

@@ -9,7 +9,7 @@ describe("tokenProvider", () => {
resourceId: "", resourceId: "",
resourceType: "dbs" as ResourceType, resourceType: "dbs" as ResourceType,
headers: {}, headers: {},
getAuthorizationTokenUsingMasterKey: () => "", getAuthorizationTokenUsingMasterKey: () => ""
}; };
beforeEach(() => { beforeEach(() => {
@@ -17,7 +17,7 @@ describe("tokenProvider", () => {
window.fetch = jest.fn().mockImplementation(() => { window.fetch = jest.fn().mockImplementation(() => {
return { return {
json: () => "{}", json: () => "{}",
headers: new Map(), headers: new Map()
}; };
}); });
}); });
@@ -45,7 +45,7 @@ describe("getTokenFromAuthService", () => {
window.fetch = jest.fn().mockImplementation(() => { window.fetch = jest.fn().mockImplementation(() => {
return { return {
json: () => "{}", json: () => "{}",
headers: new Map(), headers: new Map()
}; };
}); });
}); });
@@ -86,8 +86,8 @@ describe("endpoint", () => {
documentEndpoint: "bar", documentEndpoint: "bar",
gremlinEndpoint: "foo", gremlinEndpoint: "foo",
tableEndpoint: "foo", tableEndpoint: "foo",
cassandraEndpoint: "foo", cassandraEndpoint: "foo"
}, }
}); });
expect(endpoint()).toEqual("bar"); expect(endpoint()).toEqual("bar");
}); });

View File

@@ -63,13 +63,13 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
method: "POST", method: "POST",
headers: { headers: {
"content-type": "application/json", "content-type": "application/json",
"x-ms-encrypted-auth-token": _accessToken, "x-ms-encrypted-auth-token": _accessToken
}, },
body: JSON.stringify({ body: JSON.stringify({
verb, verb,
resourceType, resourceType,
resourceId, resourceId
}), })
}); });
//TODO I am not sure why we have to parse the JSON again here. fetch should do it for us when we call .json() //TODO I am not sure why we have to parse the JSON again here. fetch should do it for us when we call .json()
const result = JSON.parse(await response.json()); const result = JSON.parse(await response.json());
@@ -93,9 +93,9 @@ export const CosmosClient = {
key: _masterKey, key: _masterKey,
tokenProvider, tokenProvider,
connectionPolicy: { connectionPolicy: {
enableEndpointDiscovery: false, enableEndpointDiscovery: false
}, },
userAgentSuffix: "Azure Portal", userAgentSuffix: "Azure Portal"
}; };
// In development we proxy requests to the backend via webpack. This is removed in production bundles. // In development we proxy requests to the backend via webpack. This is removed in production bundles.
@@ -176,5 +176,5 @@ export const CosmosClient = {
_client = null; _client = null;
_resourceToken = value; _resourceToken = value;
return value; return value;
}, }
}; };

View File

@@ -14,7 +14,7 @@ import {
PartitionKeyDefinition, PartitionKeyDefinition,
QueryIterator, QueryIterator,
Resource, Resource,
TriggerDefinition, TriggerDefinition
} from "@azure/cosmos"; } from "@azure/cosmos";
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest"; import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
import { CosmosClient } from "./CosmosClient"; import { CosmosClient } from "./CosmosClient";
@@ -68,7 +68,7 @@ export abstract class DataAccessUtilityBase {
.container(collection.id()) .container(collection.id())
.scripts.storedProcedures.readAll(options) .scripts.storedProcedures.readAll(options)
.fetchAll() .fetchAll()
.then((response) => response.resources as DataModels.StoredProcedure[]) .then(response => response.resources as DataModels.StoredProcedure[])
); );
} }
@@ -83,7 +83,7 @@ export abstract class DataAccessUtilityBase {
.container(collection.id()) .container(collection.id())
.scripts.storedProcedure(requestedResource.id) .scripts.storedProcedure(requestedResource.id)
.read(options) .read(options)
.then((response) => response.resource as DataModels.StoredProcedure) .then(response => response.resource as DataModels.StoredProcedure)
); );
} }
public readUserDefinedFunctions( public readUserDefinedFunctions(
@@ -96,7 +96,7 @@ export abstract class DataAccessUtilityBase {
.container(collection.id()) .container(collection.id())
.scripts.userDefinedFunctions.readAll(options) .scripts.userDefinedFunctions.readAll(options)
.fetchAll() .fetchAll()
.then((response) => response.resources as DataModels.UserDefinedFunction[]) .then(response => response.resources as DataModels.UserDefinedFunction[])
); );
} }
public readUserDefinedFunction( public readUserDefinedFunction(
@@ -110,7 +110,7 @@ export abstract class DataAccessUtilityBase {
.container(collection.id()) .container(collection.id())
.scripts.userDefinedFunction(requestedResource.id) .scripts.userDefinedFunction(requestedResource.id)
.read(options) .read(options)
.then((response) => response.resource as DataModels.UserDefinedFunction) .then(response => response.resource as DataModels.UserDefinedFunction)
); );
} }
@@ -121,7 +121,7 @@ export abstract class DataAccessUtilityBase {
.container(collection.id()) .container(collection.id())
.scripts.triggers.readAll(options) .scripts.triggers.readAll(options)
.fetchAll() .fetchAll()
.then((response) => response.resources as DataModels.Trigger[]) .then(response => response.resources as DataModels.Trigger[])
); );
} }
@@ -136,7 +136,7 @@ export abstract class DataAccessUtilityBase {
.container(collection.id()) .container(collection.id())
.scripts.trigger(requestedResource.id) .scripts.trigger(requestedResource.id)
.read(options) .read(options)
.then((response) => response.resource as DataModels.Trigger) .then(response => response.resource as DataModels.Trigger)
); );
} }
@@ -154,13 +154,13 @@ export abstract class DataAccessUtilityBase {
.container(collection.id()) .container(collection.id())
.scripts.storedProcedure(storedProcedure.id()) .scripts.storedProcedure(storedProcedure.id())
.execute(partitionKeyValue, params, { enableScriptLogging: true }) .execute(partitionKeyValue, params, { enableScriptLogging: true })
.then((response) => .then(response =>
deferred.resolve({ deferred.resolve({
result: response.resource, result: response.resource,
scriptLogs: response.headers[Constants.HttpHeaders.scriptLogResults], scriptLogs: response.headers[Constants.HttpHeaders.scriptLogResults]
}) })
) )
.catch((error) => deferred.reject(error)); .catch(error => deferred.reject(error));
return deferred.promise.timeout( return deferred.promise.timeout(
Constants.ClientDefaults.requestTimeoutMs, Constants.ClientDefaults.requestTimeoutMs,
@@ -177,7 +177,7 @@ export abstract class DataAccessUtilityBase {
.container(collection.id()) .container(collection.id())
.item(documentId.id(), partitionKey) .item(documentId.id(), partitionKey)
.read() .read()
.then((response) => response.resource) .then(response => response.resource)
); );
} }
@@ -230,7 +230,7 @@ export abstract class DataAccessUtilityBase {
.container(collection.id()) .container(collection.id())
.item(documentId.id(), partitionKey) .item(documentId.id(), partitionKey)
.replace(newDocument) .replace(newDocument)
.then((response) => response.resource) .then(response => response.resource)
); );
} }
@@ -243,7 +243,7 @@ export abstract class DataAccessUtilityBase {
CosmosClient.client() CosmosClient.client()
.offer(offer.id) .offer(offer.id)
.replace(newOffer, options) .replace(newOffer, options)
.then((response) => { .then(response => {
return Promise.all([this.refreshCachedOffers(), this.refreshCachedResources()]).then(() => response.resource); return Promise.all([this.refreshCachedOffers(), this.refreshCachedResources()]).then(() => response.resource);
}) })
); );
@@ -260,7 +260,7 @@ export abstract class DataAccessUtilityBase {
.container(collection.id()) .container(collection.id())
.scripts.storedProcedure(storedProcedure.id) .scripts.storedProcedure(storedProcedure.id)
.replace(storedProcedure, options) .replace(storedProcedure, options)
.then((response) => response.resource as DataModels.StoredProcedure) .then(response => response.resource as DataModels.StoredProcedure)
); );
} }
@@ -275,7 +275,7 @@ export abstract class DataAccessUtilityBase {
.container(collection.id()) .container(collection.id())
.scripts.userDefinedFunction(userDefinedFunction.id) .scripts.userDefinedFunction(userDefinedFunction.id)
.replace(userDefinedFunction, options) .replace(userDefinedFunction, options)
.then((response) => response.resource as DataModels.StoredProcedure) .then(response => response.resource as DataModels.StoredProcedure)
); );
} }
@@ -290,7 +290,7 @@ export abstract class DataAccessUtilityBase {
.container(collection.id()) .container(collection.id())
.scripts.trigger(trigger.id) .scripts.trigger(trigger.id)
.replace(trigger as TriggerDefinition, options) .replace(trigger as TriggerDefinition, options)
.then((response) => response.resource as DataModels.Trigger) .then(response => response.resource as DataModels.Trigger)
); );
} }
@@ -300,7 +300,7 @@ export abstract class DataAccessUtilityBase {
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.items.create(newDocument) .items.create(newDocument)
.then((response) => response.resource as DataModels.StoredProcedure) .then(response => response.resource as DataModels.StoredProcedure)
); );
} }
@@ -314,7 +314,7 @@ export abstract class DataAccessUtilityBase {
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.storedProcedures.create(newStoredProcedure, options) .scripts.storedProcedures.create(newStoredProcedure, options)
.then((response) => response.resource as DataModels.StoredProcedure) .then(response => response.resource as DataModels.StoredProcedure)
); );
} }
@@ -328,7 +328,7 @@ export abstract class DataAccessUtilityBase {
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.userDefinedFunctions.create(newUserDefinedFunction, options) .scripts.userDefinedFunctions.create(newUserDefinedFunction, options)
.then((response) => response.resource as DataModels.UserDefinedFunction) .then(response => response.resource as DataModels.UserDefinedFunction)
); );
} }
@@ -342,7 +342,7 @@ export abstract class DataAccessUtilityBase {
.database(collection.databaseId) .database(collection.databaseId)
.container(collection.id()) .container(collection.id())
.scripts.triggers.create(newTrigger as TriggerDefinition, options) .scripts.triggers.create(newTrigger as TriggerDefinition, options)
.then((response) => response.resource as DataModels.Trigger) .then(response => response.resource as DataModels.Trigger)
); );
} }
@@ -437,7 +437,7 @@ export abstract class DataAccessUtilityBase {
.database(database.id()) .database(database.id())
.containers.readAll() .containers.readAll()
.fetchAll() .fetchAll()
.then((response) => response.resources as DataModels.Collection[]) .then(response => response.resources as DataModels.Collection[])
); );
} }
@@ -447,7 +447,7 @@ export abstract class DataAccessUtilityBase {
.database(databaseId) .database(databaseId)
.container(collectionId) .container(collectionId)
.read() .read()
.then((response) => response.resource) .then(response => response.resource)
); );
} }
@@ -491,7 +491,7 @@ export abstract class DataAccessUtilityBase {
CosmosClient.client() CosmosClient.client()
.offers.readAll() .offers.readAll()
.fetchAll() .fetchAll()
.then((response) => response.resources) .then(response => response.resources)
); );
} }
@@ -506,7 +506,7 @@ export abstract class DataAccessUtilityBase {
CosmosClient.client() CosmosClient.client()
.offer(requestedResource.id) .offer(requestedResource.id)
.read(options) .read(options)
.then((response) => ({ ...response.resource, headers: response.headers })) .then(response => ({ ...response.resource, headers: response.headers }))
); );
} }
@@ -515,7 +515,7 @@ export abstract class DataAccessUtilityBase {
CosmosClient.client() CosmosClient.client()
.databases.readAll() .databases.readAll()
.fetchAll() .fetchAll()
.then((response) => response.resources as DataModels.Database[]) .then(response => response.resources as DataModels.Database[])
); );
} }
@@ -533,11 +533,11 @@ export abstract class DataAccessUtilityBase {
uniqueKeyPolicy, uniqueKeyPolicy,
offerThroughput, offerThroughput,
analyticalStorageTtl, analyticalStorageTtl,
hasAutoPilotV2FeatureFlag, hasAutoPilotV2FeatureFlag
} = request; } = request;
const createBody: DatabaseRequest = { const createBody: DatabaseRequest = {
id: databaseId, id: databaseId
}; };
// TODO: replace when SDK support autopilot // TODO: replace when SDK support autopilot
@@ -545,11 +545,11 @@ export abstract class DataAccessUtilityBase {
? !hasAutoPilotV2FeatureFlag ? !hasAutoPilotV2FeatureFlag
? { ? {
[Constants.HttpHeaders.autoPilotThroughputSDK]: JSON.stringify({ [Constants.HttpHeaders.autoPilotThroughputSDK]: JSON.stringify({
maxThroughput: request.autoPilot.maxThroughput, maxThroughput: request.autoPilot.maxThroughput
}), })
} }
: { : {
[Constants.HttpHeaders.autoPilotTier]: request.autoPilot.autopilotTier, [Constants.HttpHeaders.autoPilotTier]: request.autoPilot.autopilotTier
} }
: undefined; : undefined;
if (databaseLevelThroughput) { if (databaseLevelThroughput) {
@@ -562,7 +562,7 @@ export abstract class DataAccessUtilityBase {
return Q( return Q(
CosmosClient.client() CosmosClient.client()
.databases.createIfNotExists(createBody, databaseOptions) .databases.createIfNotExists(createBody, databaseOptions)
.then((response) => { .then(response => {
return response.database.containers.create( return response.database.containers.create(
{ {
id: collectionId, id: collectionId,
@@ -570,14 +570,14 @@ export abstract class DataAccessUtilityBase {
indexingPolicy: indexingPolicy ? indexingPolicy : undefined, indexingPolicy: indexingPolicy ? indexingPolicy : undefined,
uniqueKeyPolicy: uniqueKeyPolicy ? uniqueKeyPolicy : undefined, uniqueKeyPolicy: uniqueKeyPolicy ? uniqueKeyPolicy : undefined,
analyticalStorageTtl: analyticalStorageTtl, analyticalStorageTtl: analyticalStorageTtl,
throughput: databaseLevelThroughput || request.autoPilot ? undefined : offerThroughput, throughput: databaseLevelThroughput || request.autoPilot ? undefined : offerThroughput
} as ContainerRequest, // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed } as ContainerRequest, // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed
{ {
initialHeaders: databaseLevelThroughput ? undefined : initialHeaders, initialHeaders: databaseLevelThroughput ? undefined : initialHeaders
} }
); );
}) })
.then((containerResponse) => containerResponse.resource) .then(containerResponse => containerResponse.resource)
.finally(() => this.refreshCachedResources(options)) .finally(() => this.refreshCachedResources(options))
); );
} }
@@ -591,7 +591,7 @@ export abstract class DataAccessUtilityBase {
deferred.resolve(createdDatabase); deferred.resolve(createdDatabase);
}); });
}, },
(_createDatabaseError) => { _createDatabaseError => {
deferred.reject(_createDatabaseError); deferred.reject(_createDatabaseError);
} }
); );
@@ -654,10 +654,10 @@ export abstract class DataAccessUtilityBase {
const initialHeaders = autoPilot const initialHeaders = autoPilot
? !hasAutoPilotV2FeatureFlag ? !hasAutoPilotV2FeatureFlag
? { ? {
[Constants.HttpHeaders.autoPilotThroughputSDK]: JSON.stringify({ maxThroughput: autoPilot.maxThroughput }), [Constants.HttpHeaders.autoPilotThroughputSDK]: JSON.stringify({ maxThroughput: autoPilot.maxThroughput })
} }
: { : {
[Constants.HttpHeaders.autoPilotTier]: autoPilot.autopilotTier, [Constants.HttpHeaders.autoPilotTier]: autoPilot.autopilotTier
} }
: undefined; : undefined;
if (!!databaseLevelThroughput) { if (!!databaseLevelThroughput) {

View File

@@ -6,7 +6,7 @@ import Q from "q";
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { DataAccessUtilityBase } from "./DataAccessUtilityBase"; import { DataAccessUtilityBase } from "./DataAccessUtilityBase";
import * as Logger from "./Logger"; import { Logger } from "./Logger";
import { MessageHandler } from "./MessageHandler"; import { MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities"; import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
@@ -376,7 +376,7 @@ export default class DocumentClientUtilityBase {
JSON.stringify({ JSON.stringify({
oldOffer: offer, oldOffer: offer,
newOffer: newOffer, newOffer: newOffer,
error: error, error: error
}), }),
"UpdateOffer", "UpdateOffer",
error.code error.code
@@ -583,7 +583,7 @@ export default class DocumentClientUtilityBase {
); );
deferred.resolve(createdStoredProcedure); deferred.resolve(createdStoredProcedure);
}, },
(error) => { error => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Error while creating stored procedure for container ${collection.id()}:\n ${JSON.stringify(error)}` `Error while creating stored procedure for container ${collection.id()}:\n ${JSON.stringify(error)}`
@@ -620,7 +620,7 @@ export default class DocumentClientUtilityBase {
); );
deferred.resolve(createdUserDefinedFunction); deferred.resolve(createdUserDefinedFunction);
}, },
(error) => { error => {
NotificationConsoleUtils.logConsoleMessage( NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error, ConsoleDataType.Error,
`Error while creating user defined function for container ${collection.id()}:\n ${JSON.stringify(error)}` `Error while creating user defined function for container ${collection.id()}:\n ${JSON.stringify(error)}`
@@ -1176,7 +1176,7 @@ export default class DocumentClientUtilityBase {
} }
MessageHandler.sendMessage({ MessageHandler.sendMessage({
type: MessageTypes.ForbiddenError, type: MessageTypes.ForbiddenError,
reason: error && error.message ? error.message : error, reason: error && error.message ? error.message : error
}); });
} }
} }

View File

@@ -73,7 +73,7 @@ export default class EditableUtility {
return false; return false;
}); });
observable.subscribe((edit) => { observable.subscribe(edit => {
var edits = observable.edits && observable.edits(); var edits = observable.edits && observable.edits();
if (!edits) { if (!edits) {
return; return;
@@ -83,9 +83,9 @@ export default class EditableUtility {
}); });
observable.editableIsValid = ko.observable<boolean>(true); observable.editableIsValid = ko.observable<boolean>(true);
observable.subscribe((value) => { observable.subscribe(value => {
const validations: ((value: T) => boolean)[] = (observable.validations && observable.validations()) || []; const validations: ((value: T) => boolean)[] = (observable.validations && observable.validations()) || [];
const isValid = validations.every((validate) => validate(value)); const isValid = validations.every(validate => validate(value));
observable.editableIsValid(isValid); observable.editableIsValid(isValid);
}); });

View File

@@ -11,7 +11,7 @@ describe("Error Parser Utility", () => {
err.code = 400; err.code = 400;
err.body = { err.body = {
code: "BadRequest", code: "BadRequest",
message, message
}; };
err.headers = {}; err.headers = {};
err.activityId = "97b2e684-7505-4921-85f6-2513b9b28220"; err.activityId = "97b2e684-7505-4921-85f6-2513b9b28220";

View File

@@ -26,7 +26,7 @@ function _parse(err: any): DataModels.ErrorDataModel[] {
normalizedErrors.push(err); normalizedErrors.push(err);
} else { } else {
const innerErrors: any[] = _getInnerErrors(err.message); const innerErrors: any[] = _getInnerErrors(err.message);
normalizedErrors = innerErrors.map((innerError) => normalizedErrors = innerErrors.map(innerError =>
typeof innerError === "string" ? { message: innerError } : innerError typeof innerError === "string" ? { message: innerError } : innerError
); );
} }

View File

@@ -11,8 +11,8 @@ describe("nextPage", () => {
queryMetrics: {}, queryMetrics: {},
requestCharge: 1, requestCharge: 1,
headers: {}, headers: {},
activityId: "foo", activityId: "foo"
}), })
}; };
expect(await nextPage(fakeIterator, 10)).toMatchSnapshot(); expect(await nextPage(fakeIterator, 10)).toMatchSnapshot();

View File

@@ -14,7 +14,7 @@ export interface MinimalQueryIterator {
// Pick<QueryIterator<any>, "fetchNext">; // Pick<QueryIterator<any>, "fetchNext">;
export function nextPage(documentsIterator: MinimalQueryIterator, firstItemIndex: number): Promise<QueryResults> { export function nextPage(documentsIterator: MinimalQueryIterator, firstItemIndex: number): Promise<QueryResults> {
return documentsIterator.fetchNext().then((response) => { return documentsIterator.fetchNext().then(response => {
const documents = response.resources; const documents = response.resources;
const headers = (response as any).headers || {}; // TODO this is a private key. Remove any const headers = (response as any).headers || {}; // TODO this is a private key. Remove any
const itemCount = (documents && documents.length) || 0; const itemCount = (documents && documents.length) || 0;
@@ -26,7 +26,7 @@ export function nextPage(documentsIterator: MinimalQueryIterator, firstItemIndex
lastItemIndex: Number(firstItemIndex) + Number(itemCount), lastItemIndex: Number(firstItemIndex) + Number(itemCount),
headers, headers,
activityId: response.activityId, activityId: response.activityId,
requestCharge: response.requestCharge, requestCharge: response.requestCharge
}; };
}); });
} }

View File

@@ -1,5 +1,5 @@
import { LogEntryLevel } from "../Contracts/Diagnostics"; import { LogEntryLevel } from "../Contracts/Diagnostics";
import * as Logger from "./Logger"; import { Logger } from "./Logger";
import { MessageHandler } from "./MessageHandler"; import { MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";

View File

@@ -4,68 +4,84 @@ import { appInsights } from "../Shared/appInsights";
import { SeverityLevel } from "@microsoft/applicationinsights-web"; import { SeverityLevel } from "@microsoft/applicationinsights-web";
// TODO: Move to a separate Diagnostics folder // TODO: Move to a separate Diagnostics folder
// eslint-disable-next-line @typescript-eslint/no-explicit-any export class Logger {
export function logInfo(message: string | Record<string, any>, area: string, code?: number): void { public static logInfo(message: string | Record<string, any>, area: string, code?: number): void {
let logMessage: string; let logMessage: string;
if (typeof message === "string") { if (typeof message === "string") {
logMessage = message; logMessage = message;
} else { } else {
logMessage = JSON.stringify(message, Object.getOwnPropertyNames(message)); logMessage = JSON.stringify(message, Object.getOwnPropertyNames(message));
}
const entry: Diagnostics.LogEntry = _generateLogEntry(Diagnostics.LogEntryLevel.Verbose, logMessage, area, code);
return _logEntry(entry);
}
export function logWarning(message: string, area: string, code?: number): void {
const entry: Diagnostics.LogEntry = _generateLogEntry(Diagnostics.LogEntryLevel.Warning, message, area, code);
return _logEntry(entry);
}
export function logError(message: string | Error, area: string, code?: number): void {
let logMessage: string;
if (typeof message === "string") {
logMessage = message;
} else {
logMessage = JSON.stringify(message, Object.getOwnPropertyNames(message));
}
const entry: Diagnostics.LogEntry = _generateLogEntry(Diagnostics.LogEntryLevel.Error, logMessage, area, code);
return _logEntry(entry);
}
function _logEntry(entry: Diagnostics.LogEntry): void {
MessageHandler.sendMessage({
type: MessageTypes.LogInfo,
data: JSON.stringify(entry),
});
const severityLevel = ((level: Diagnostics.LogEntryLevel): SeverityLevel => {
switch (level) {
case Diagnostics.LogEntryLevel.Custom:
case Diagnostics.LogEntryLevel.Debug:
case Diagnostics.LogEntryLevel.Verbose:
return SeverityLevel.Verbose;
case Diagnostics.LogEntryLevel.Warning:
return SeverityLevel.Warning;
case Diagnostics.LogEntryLevel.Error:
return SeverityLevel.Error;
default:
return SeverityLevel.Information;
} }
})(entry.level); const entry: Diagnostics.LogEntry = Logger._generateLogEntry(
appInsights.trackTrace({ message: entry.message, severityLevel }, { area: entry.area }); Diagnostics.LogEntryLevel.Verbose,
} logMessage,
area,
code
);
return Logger._logEntry(entry);
}
function _generateLogEntry( public static logWarning(message: string, area: string, code?: number): void {
level: Diagnostics.LogEntryLevel, const entry: Diagnostics.LogEntry = Logger._generateLogEntry(
message: string, Diagnostics.LogEntryLevel.Warning,
area: string, message,
code: number area,
): Diagnostics.LogEntry { code
return { );
timestamp: new Date().getUTCSeconds(), return Logger._logEntry(entry);
level: level, }
message: message,
area: area, public static logError(message: string | Error, area: string, code?: number): void {
code: code, let logMessage: string;
}; if (typeof message === "string") {
logMessage = message;
} else {
logMessage = JSON.stringify(message, Object.getOwnPropertyNames(message));
}
const entry: Diagnostics.LogEntry = Logger._generateLogEntry(
Diagnostics.LogEntryLevel.Error,
logMessage,
area,
code
);
return Logger._logEntry(entry);
}
private static _logEntry(entry: Diagnostics.LogEntry): void {
MessageHandler.sendMessage({
type: MessageTypes.LogInfo,
data: JSON.stringify(entry)
});
const severityLevel = ((level: Diagnostics.LogEntryLevel): SeverityLevel => {
switch (level) {
case Diagnostics.LogEntryLevel.Custom:
case Diagnostics.LogEntryLevel.Debug:
case Diagnostics.LogEntryLevel.Verbose:
return SeverityLevel.Verbose;
case Diagnostics.LogEntryLevel.Warning:
return SeverityLevel.Warning;
case Diagnostics.LogEntryLevel.Error:
return SeverityLevel.Error;
default:
return SeverityLevel.Information;
}
})(entry.level);
appInsights.trackTrace({ message: entry.message, severityLevel }, { area: entry.area });
}
private static _generateLogEntry(
level: Diagnostics.LogEntryLevel,
message: string,
area: string,
code: number
): Diagnostics.LogEntry {
return {
timestamp: new Date().getUTCSeconds(),
level: level,
message: message,
area: area,
code: code
};
}
} }

View File

@@ -41,7 +41,7 @@ describe("Message Handler", () => {
let mockPromise: CachedDataPromise<any> = { let mockPromise: CachedDataPromise<any> = {
id: "123", id: "123",
startTime: new Date(), startTime: new Date(),
deferred: Q.defer<any>(), deferred: Q.defer<any>()
}; };
let mockMessage = { message: { id: "123", data: "{}" } }; let mockMessage = { message: { id: "123", data: "{}" } };
@@ -54,7 +54,7 @@ describe("Message Handler", () => {
let mockPromise: CachedDataPromise<any> = { let mockPromise: CachedDataPromise<any> = {
id: "123", id: "123",
startTime: new Date(), startTime: new Date(),
deferred: Q.defer<any>(), deferred: Q.defer<any>()
}; };
MockMessageHandler.addToMap(mockPromise.id, mockPromise); MockMessageHandler.addToMap(mockPromise.id, mockPromise);

View File

@@ -46,7 +46,7 @@ export class MessageHandler {
let cachedDataPromise: CachedDataPromise<TResponseDataModel> = { let cachedDataPromise: CachedDataPromise<TResponseDataModel> = {
deferred: Q.defer<TResponseDataModel>(), deferred: Q.defer<TResponseDataModel>(),
startTime: new Date(), startTime: new Date(),
id: _.uniqueId(), id: _.uniqueId()
}; };
MessageHandler.RequestMap[cachedDataPromise.id] = cachedDataPromise; MessageHandler.RequestMap[cachedDataPromise.id] = cachedDataPromise;
MessageHandler.sendMessage({ type: messageType, params: params, id: cachedDataPromise.id }); MessageHandler.sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
@@ -63,7 +63,7 @@ export class MessageHandler {
window.parent.postMessage( window.parent.postMessage(
{ {
signature: "pcIframe", signature: "pcIframe",
data: data, data: data
}, },
window.document.referrer window.document.referrer
); );

View File

@@ -4,7 +4,7 @@ import {
getEndpoint, getEndpoint,
queryDocuments, queryDocuments,
readDocument, readDocument,
updateDocument, updateDocument
} from "./MongoProxyClient"; } from "./MongoProxyClient";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import { Collection, DatabaseAccount, DocumentId } from "../Contracts/ViewModels"; import { Collection, DatabaseAccount, DocumentId } from "../Contracts/ViewModels";
@@ -20,7 +20,7 @@ const fetchMock = () => {
ok: true, ok: true,
text: () => "{}", text: () => "{}",
json: () => "{}", json: () => "{}",
headers: new Map(), headers: new Map()
}); });
}; };
@@ -33,8 +33,8 @@ const collection = {
partitionKey: { partitionKey: {
paths: ["/pk"], paths: ["/pk"],
kind: "Hash", kind: "Hash",
version: 1, version: 1
}, }
} as Collection; } as Collection;
const documentId = ({ const documentId = ({
@@ -44,8 +44,8 @@ const documentId = ({
partitionKey: { partitionKey: {
paths: ["/pk"], paths: ["/pk"],
kind: "Hash", kind: "Hash",
version: 1, version: 1
}, }
} as unknown) as DocumentId; } as unknown) as DocumentId;
const databaseAccount = { const databaseAccount = {
@@ -58,8 +58,8 @@ const databaseAccount = {
documentEndpoint: "bar", documentEndpoint: "bar",
gremlinEndpoint: "foo", gremlinEndpoint: "foo",
tableEndpoint: "foo", tableEndpoint: "foo",
cassandraEndpoint: "foo", cassandraEndpoint: "foo"
}, }
}; };
describe("MongoProxyClient", () => { describe("MongoProxyClient", () => {
@@ -69,7 +69,7 @@ describe("MongoProxyClient", () => {
CosmosClient.databaseAccount(databaseAccount as any); CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "", serverId: () => ""
} as any; } as any;
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -100,7 +100,7 @@ describe("MongoProxyClient", () => {
CosmosClient.databaseAccount(databaseAccount as any); CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "", serverId: () => ""
} as any; } as any;
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -131,7 +131,7 @@ describe("MongoProxyClient", () => {
CosmosClient.databaseAccount(databaseAccount as any); CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "", serverId: () => ""
} as any; } as any;
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -162,7 +162,7 @@ describe("MongoProxyClient", () => {
CosmosClient.databaseAccount(databaseAccount as any); CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "", serverId: () => ""
} as any; } as any;
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -193,7 +193,7 @@ describe("MongoProxyClient", () => {
CosmosClient.databaseAccount(databaseAccount as any); CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "", serverId: () => ""
} as any; } as any;
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -225,7 +225,7 @@ describe("MongoProxyClient", () => {
CosmosClient.databaseAccount(databaseAccount as any); CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = { window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com", extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => "", serverId: () => ""
} as any; } as any;
}); });
@@ -259,7 +259,7 @@ describe("MongoProxyClient", () => {
sid: "a2", sid: "a2",
rg: "c1", rg: "c1",
dba: "main", dba: "main",
is: false, is: false
}; };
_createMongoCollectionWithARM("management.azure.com", properties, { "x-ms-cosmos-offer-autopilot-tier": "1" }); _createMongoCollectionWithARM("management.azure.com", properties, { "x-ms-cosmos-offer-autopilot-tier": "1" });
expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith( expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith(
@@ -268,8 +268,8 @@ describe("MongoProxyClient", () => {
{ {
properties: { properties: {
options: { "x-ms-cosmos-offer-autopilot-tier": "1" }, options: { "x-ms-cosmos-offer-autopilot-tier": "1" },
resource: { id: "abc-collection" }, resource: { id: "abc-collection" }
}, }
} }
); );
}); });
@@ -285,7 +285,7 @@ describe("MongoProxyClient", () => {
rg: "c1", rg: "c1",
dba: "main", dba: "main",
is: false, is: false,
offerThroughput: 400, offerThroughput: 400
}; };
_createMongoCollectionWithARM("management.azure.com", properties, undefined); _createMongoCollectionWithARM("management.azure.com", properties, undefined);
expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith( expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith(
@@ -294,8 +294,8 @@ describe("MongoProxyClient", () => {
{ {
properties: { properties: {
options: { throughput: "400" }, options: { throughput: "400" },
resource: { id: "abc-collection" }, resource: { id: "abc-collection" }
}, }
} }
); );
}); });

View File

@@ -20,7 +20,7 @@ import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClie
const defaultHeaders = { const defaultHeaders = {
[HttpHeaders.apiType]: ApiType.MongoDB.toString(), [HttpHeaders.apiType]: ApiType.MongoDB.toString(),
[CosmosSDKConstants.HttpHeaders.MaxEntityCount]: "100", [CosmosSDKConstants.HttpHeaders.MaxEntityCount]: "100",
[CosmosSDKConstants.HttpHeaders.Version]: "2017-11-15", [CosmosSDKConstants.HttpHeaders.Version]: "2017-11-15"
}; };
function authHeaders(): any { function authHeaders(): any {
@@ -31,13 +31,13 @@ function authHeaders(): any {
} }
} }
export function queryIterator(databaseId: string, collection: Collection, query: string): any { export function queryIterator(databaseId: string, collection: Collection, query: string) {
let continuationToken: string; let continuationToken: string = null;
return { return {
fetchNext: () => { fetchNext: () => {
return queryDocuments(databaseId, collection, false, query).then((response) => { return queryDocuments(databaseId, collection, false, query).then(response => {
continuationToken = response.continuationToken; continuationToken = response.continuationToken;
const headers = {} as any; let headers = {} as any;
response.headers.forEach((value: any, key: any) => { response.headers.forEach((value: any, key: any) => {
headers[key] = value; headers[key] = value;
}); });
@@ -46,10 +46,10 @@ export function queryIterator(databaseId: string, collection: Collection, query:
headers, headers,
requestCharge: headers[CosmosSDKConstants.HttpHeaders.RequestCharge], requestCharge: headers[CosmosSDKConstants.HttpHeaders.RequestCharge],
activityId: headers[CosmosSDKConstants.HttpHeaders.ActivityId], activityId: headers[CosmosSDKConstants.HttpHeaders.ActivityId],
hasMoreResults: !!continuationToken, hasMoreResults: !!continuationToken
}; };
}); });
}, }
}; };
} }
@@ -78,9 +78,7 @@ export function queryDocuments(
rg: CosmosClient.resourceGroup(), rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
pk: pk:
collection && collection.partitionKey && !collection.partitionKey.systemKey collection && collection.partitionKey && !collection.partitionKey.systemKey ? collection.partitionKeyProperty : ""
? collection.partitionKeyProperty
: "",
}; };
const endpoint = getEndpoint(databaseAccount) || ""; const endpoint = getEndpoint(databaseAccount) || "";
@@ -93,7 +91,7 @@ export function queryDocuments(
[CosmosSDKConstants.HttpHeaders.EnableScanInQuery]: "true", [CosmosSDKConstants.HttpHeaders.EnableScanInQuery]: "true",
[CosmosSDKConstants.HttpHeaders.EnableCrossPartitionQuery]: "true", [CosmosSDKConstants.HttpHeaders.EnableCrossPartitionQuery]: "true",
[CosmosSDKConstants.HttpHeaders.ParallelizeCrossPartitionQuery]: "true", [CosmosSDKConstants.HttpHeaders.ParallelizeCrossPartitionQuery]: "true",
[HttpHeaders.contentType]: "application/query+json", [HttpHeaders.contentType]: "application/query+json"
}; };
if (continuationToken) { if (continuationToken) {
@@ -106,17 +104,24 @@ export function queryDocuments(
.fetch(`${endpoint}${path}?${queryString.stringify(params)}`, { .fetch(`${endpoint}${path}?${queryString.stringify(params)}`, {
method: "POST", method: "POST",
body: JSON.stringify({ query }), body: JSON.stringify({ query }),
headers, headers
}) })
.then(async (response) => { .then(async response => {
if (response.ok) { if (response.ok) {
return { return {
continuationToken: response.headers.get(CosmosSDKConstants.HttpHeaders.Continuation), continuationToken: response.headers.get(CosmosSDKConstants.HttpHeaders.Continuation),
documents: (await response.json()).Documents as DataModels.DocumentId[], documents: (await response.json()).Documents as DataModels.DocumentId[],
headers: response.headers, headers: response.headers
}; };
} }
return errorHandling(response, "querying documents", params); const errorMessage = await response.text();
if (response.status === HttpStatusCodes.Forbidden) {
MessageHandler.sendMessage({
type: MessageTypes.ForbiddenError,
reason: errorMessage
});
}
throw new Error(errorMessage);
}); });
} }
@@ -140,9 +145,7 @@ export function readDocument(
rg: CosmosClient.resourceGroup(), rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
pk: pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
? documentId.partitionKeyProperty
: "",
}; };
const endpoint = getEndpoint(databaseAccount); const endpoint = getEndpoint(databaseAccount);
@@ -154,14 +157,14 @@ export function readDocument(
...authHeaders(), ...authHeaders(),
[CosmosSDKConstants.HttpHeaders.PartitionKey]: encodeURIComponent( [CosmosSDKConstants.HttpHeaders.PartitionKey]: encodeURIComponent(
JSON.stringify(documentId.partitionKeyHeader()) JSON.stringify(documentId.partitionKeyHeader())
), )
}, }
}) })
.then((response) => { .then(async response => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
return errorHandling(response, "reading document", params); errorHandling(response);
}); });
} }
@@ -182,7 +185,7 @@ export function createDocument(
sid: CosmosClient.subscriptionId(), sid: CosmosClient.subscriptionId(),
rg: CosmosClient.resourceGroup(), rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "", pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : ""
}; };
const endpoint = getEndpoint(databaseAccount); const endpoint = getEndpoint(databaseAccount);
@@ -193,14 +196,14 @@ export function createDocument(
body: JSON.stringify(documentContent), body: JSON.stringify(documentContent),
headers: { headers: {
...defaultHeaders, ...defaultHeaders,
...authHeaders(), ...authHeaders()
}, }
}) })
.then((response) => { .then(async response => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
return errorHandling(response, "creating document", params); errorHandling(response);
}); });
} }
@@ -225,9 +228,7 @@ export function updateDocument(
rg: CosmosClient.resourceGroup(), rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
pk: pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
? documentId.partitionKeyProperty
: "",
}; };
const endpoint = getEndpoint(databaseAccount); const endpoint = getEndpoint(databaseAccount);
@@ -239,14 +240,14 @@ export function updateDocument(
...defaultHeaders, ...defaultHeaders,
...authHeaders(), ...authHeaders(),
[HttpHeaders.contentType]: "application/json", [HttpHeaders.contentType]: "application/json",
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()), [CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader())
}, }
}) })
.then((response) => { .then(async response => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
return errorHandling(response, "updating document", params); errorHandling(response);
}); });
} }
@@ -270,9 +271,7 @@ export function deleteDocument(
rg: CosmosClient.resourceGroup(), rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
pk: pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
? documentId.partitionKeyProperty
: "",
}; };
const endpoint = getEndpoint(databaseAccount); const endpoint = getEndpoint(databaseAccount);
@@ -283,14 +282,14 @@ export function deleteDocument(
...defaultHeaders, ...defaultHeaders,
...authHeaders(), ...authHeaders(),
[HttpHeaders.contentType]: "application/json", [HttpHeaders.contentType]: "application/json",
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()), [CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader())
},
})
.then((response) => {
if (response.ok) {
return undefined;
} }
return errorHandling(response, "deleting document", params); })
.then(async response => {
if (response.ok) {
return;
}
errorHandling(response);
}); });
} }
@@ -319,7 +318,7 @@ export function createMongoCollectionWithProxy(
sid: CosmosClient.subscriptionId(), sid: CosmosClient.subscriptionId(),
rg: CosmosClient.resourceGroup(), rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
isAutoPilot: false, isAutoPilot: false
}; };
if (autopilotOptions) { if (autopilotOptions) {
@@ -337,15 +336,19 @@ export function createMongoCollectionWithProxy(
headers: { headers: {
...defaultHeaders, ...defaultHeaders,
...authHeaders(), ...authHeaders(),
[HttpHeaders.contentType]: "application/json", [HttpHeaders.contentType]: "application/json"
}, }
} }
) )
.then((response) => { .then(async response => {
if (response.ok) { if (response.ok) {
return undefined; return;
} }
return errorHandling(response, "creating collection", params); NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error creating collection: ${await response.json()}, Payload: ${params}`
);
errorHandling(response);
}); });
} }
@@ -376,7 +379,7 @@ export function createMongoCollectionWithARM(
sid: CosmosClient.subscriptionId(), sid: CosmosClient.subscriptionId(),
rg: CosmosClient.resourceGroup(), rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name, dba: databaseAccount.name,
analyticalStorageTtl, analyticalStorageTtl
}; };
if (createDatabase) { if (createDatabase) {
@@ -404,16 +407,13 @@ export function getEndpoint(databaseAccount: ViewModels.DatabaseAccount): string
return url; return url;
} }
async function errorHandling(response: any, action: string, params: any): Promise<any> { async function errorHandling(response: any): Promise<any> {
const errorMessage = await response.text(); const errorMessage = await response.text();
// Log the error where the user can see it
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error ${action}: ${errorMessage}, Payload: ${JSON.stringify(params)}`
);
if (response.status === HttpStatusCodes.Forbidden) { if (response.status === HttpStatusCodes.Forbidden) {
MessageHandler.sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage }); MessageHandler.sendMessage({
return; type: MessageTypes.ForbiddenError,
reason: errorMessage
});
} }
throw new Error(errorMessage); throw new Error(errorMessage);
} }
@@ -432,10 +432,10 @@ export async function _createMongoCollectionWithARM(
const rpPayloadToCreateCollection: DataModels.MongoCreationRequest = { const rpPayloadToCreateCollection: DataModels.MongoCreationRequest = {
properties: { properties: {
resource: { resource: {
id: params.coll, id: params.coll
}, },
options: {}, options: {}
}, }
}; };
if (params.is) { if (params.is) {
@@ -462,6 +462,14 @@ export async function _createMongoCollectionWithARM(
rpPayloadToCreateCollection rpPayloadToCreateCollection
); );
} catch (response) { } catch (response) {
return errorHandling(response, "creating collection", undefined); NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Error creating collection: ${JSON.stringify(response)}`
);
if (response.status === HttpStatusCodes.Forbidden) {
MessageHandler.sendMessage({ type: MessageTypes.ForbiddenError });
return;
}
throw new Error(`Error creating collection`);
} }
} }

View File

@@ -14,7 +14,7 @@
*/ */
export default class MongoUtility { export default class MongoUtility {
public static tojson = function (x: any, indent: string, nolint: boolean) { public static tojson = function(x: any, indent: string, nolint: boolean) {
if (x === null || x === undefined) { if (x === null || x === undefined) {
return String(x); return String(x);
} }
@@ -71,7 +71,7 @@ export default class MongoUtility {
} }
}; };
private static tojsonObject = function (x: any, indent: string, nolint: boolean) { private static tojsonObject = function(x: any, indent: string, nolint: boolean) {
var lineEnding = nolint ? " " : "\n"; var lineEnding = nolint ? " " : "\n";
var tabSpace = nolint ? "" : "\t"; var tabSpace = nolint ? "" : "\t";
indent = indent || ""; indent = indent || "";
@@ -114,7 +114,7 @@ export default class MongoUtility {
} }
} }
// Add proper line endings, indents, and commas to each line // Add proper line endings, indents, and commas to each line
s += $.map(pairs, function (pair) { s += $.map(pairs, function(pair) {
return lineEnding + indent + pair; return lineEnding + indent + pair;
}).join(","); }).join(",");
s += lineEnding; s += lineEnding;
@@ -124,7 +124,7 @@ export default class MongoUtility {
return s + indent + "}"; return s + indent + "}";
}; };
private static tojsonArray = function (a: any, indent: string, nolint: boolean) { private static tojsonArray = function(a: any, indent: string, nolint: boolean) {
if (a.length === 0) { if (a.length === 0) {
return "[ ]"; return "[ ]";
} }
@@ -151,7 +151,7 @@ export default class MongoUtility {
return s; return s;
}; };
private static hasDefinedProperty = function (obj: any, prop: string): boolean { private static hasDefinedProperty = function(obj: any, prop: string): boolean {
if (Object.getPrototypeOf === undefined || Object.getPrototypeOf(obj) === null) { if (Object.getPrototypeOf === undefined || Object.getPrototypeOf(obj) === null) {
return false; return false;
} else if (obj.hasOwnProperty(prop)) { } else if (obj.hasOwnProperty(prop)) {

View File

@@ -28,7 +28,7 @@ export class NotificationsClientBase implements ViewModels.NotificationsClient {
url: url, url: url,
type: "GET", type: "GET",
headers: headers, headers: headers,
cache: false, cache: false
}).then( }).then(
(notifications: DataModels.Notification[], textStatus: string, xhr: JQueryXHR<any>) => { (notifications: DataModels.Notification[], textStatus: string, xhr: JQueryXHR<any>) => {
deferred.resolve(notifications); deferred.resolve(notifications);

View File

@@ -7,7 +7,7 @@ import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { CosmosClient } from "./CosmosClient"; import { CosmosClient } from "./CosmosClient";
import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import * as Logger from "./Logger"; import { Logger } from "./Logger";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils"; import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import { QueryUtils } from "../Utils/QueryUtils"; import { QueryUtils } from "../Utils/QueryUtils";
@@ -15,7 +15,7 @@ export class QueriesClient implements ViewModels.QueriesClient {
private static readonly PartitionKey: DataModels.PartitionKey = { private static readonly PartitionKey: DataModels.PartitionKey = {
paths: [`/${SavedQueries.PartitionKeyProperty}`], paths: [`/${SavedQueries.PartitionKeyProperty}`],
kind: BackendDefaults.partitionKeyKind, kind: BackendDefaults.partitionKeyKind,
version: BackendDefaults.partitionKeyVersion, version: BackendDefaults.partitionKeyVersion
}; };
private static readonly FetchQuery: string = "SELECT * FROM c"; private static readonly FetchQuery: string = "SELECT * FROM c";
private static readonly FetchMongoQuery: string = "{}"; private static readonly FetchMongoQuery: string = "{}";
@@ -38,7 +38,7 @@ export class QueriesClient implements ViewModels.QueriesClient {
databaseId: SavedQueries.DatabaseName, databaseId: SavedQueries.DatabaseName,
partitionKey: QueriesClient.PartitionKey, partitionKey: QueriesClient.PartitionKey,
offerThroughput: SavedQueries.OfferThroughput, offerThroughput: SavedQueries.OfferThroughput,
databaseLevelThroughput: undefined, databaseLevelThroughput: undefined
}) })
.then( .then(
(collection: DataModels.Collection) => { (collection: DataModels.Collection) => {
@@ -152,7 +152,7 @@ export class QueriesClient implements ViewModels.QueriesClient {
resourceId: resourceId, resourceId: resourceId,
queryName: queryName, queryName: queryName,
query: query, query: query,
id: id, id: id
}; };
try { try {
this.validateQuery(parsedQuery); this.validateQuery(parsedQuery);
@@ -219,7 +219,7 @@ export class QueriesClient implements ViewModels.QueriesClient {
const documentId: ViewModels.DocumentId = new DocumentId( const documentId: ViewModels.DocumentId = new DocumentId(
{ {
partitionKey: QueriesClient.PartitionKey, partitionKey: QueriesClient.PartitionKey,
partitionKeyProperty: "id", partitionKeyProperty: "id"
} as ViewModels.DocumentsTab, } as ViewModels.DocumentsTab,
query, query,
query.queryName query.queryName

View File

@@ -4,7 +4,7 @@ import { SplitterMetrics } from "./Constants";
export enum SplitterDirection { export enum SplitterDirection {
Horizontal = "horizontal", Horizontal = "horizontal",
Vertical = "vertical", Vertical = "vertical"
} }
export interface SplitterBounds { export interface SplitterBounds {
@@ -50,7 +50,7 @@ export class Splitter {
animate: true, animate: true,
animateDuration: "fast", animateDuration: "fast",
start: this.onResizeStart, start: this.onResizeStart,
stop: this.onResizeStop, stop: this.onResizeStop
}; };
if (isVerticalSplitter) { if (isVerticalSplitter) {
@@ -90,7 +90,9 @@ export class Splitter {
this.lastWidth = $(this.leftSide).width(); this.lastWidth = $(this.leftSide).width();
$(this.splitter).css("left", SplitterMetrics.CollapsedPositionLeft); $(this.splitter).css("left", SplitterMetrics.CollapsedPositionLeft);
$(this.leftSide).css("width", ""); $(this.leftSide).css("width", "");
$(this.leftSide).resizable("option", "disabled", true).removeClass("ui-resizable-disabled"); // remove class so splitter is visible $(this.leftSide)
.resizable("option", "disabled", true)
.removeClass("ui-resizable-disabled"); // remove class so splitter is visible
$(this.splitter).removeClass("ui-resizable-e"); $(this.splitter).removeClass("ui-resizable-e");
this.isCollapsed(true); this.isCollapsed(true);
} }

View File

@@ -32,8 +32,8 @@ export default class UrlUtility {
type: type, type: type,
objectBody: { objectBody: {
id: id, id: id,
self: resourcePath, self: resourcePath
}, }
}; };
return result; return result;

View File

@@ -1,7 +1,7 @@
export enum Platform { export enum Platform {
Portal = "Portal", Portal = "Portal",
Hosted = "Hosted", Hosted = "Hosted",
Emulator = "Emulator", Emulator = "Emulator"
} }
interface Config { interface Config {
@@ -45,7 +45,7 @@ let config: Config = {
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net", ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306 GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
JUNO_ENDPOINT: "https://tools.cosmos.azure.com", JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
AZURESAMPLESCOSMOSDBPAT: "99e38770e29b4a61d7c49f188780504efd35cc86", //[SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification=" // this PAT is a "no scopes" PAT with zero access to any projects, this is just used to get around the dev.github.com rate limit when accessing public samples repo.")] AZURESAMPLESCOSMOSDBPAT: "99e38770e29b4a61d7c49f188780504efd35cc86" //[SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification=" // this PAT is a "no scopes" PAT with zero access to any projects, this is just used to get around the dev.github.com rate limit when accessing public samples repo.")]
}; };
// Injected for local develpment. These will be removed in the production bundle by webpack // Injected for local develpment. These will be removed in the production bundle by webpack

View File

@@ -7,7 +7,7 @@ export enum TabKind {
TableEntities, TableEntities,
Graph, Graph,
SQLQuery, SQLQuery,
ScaleSettings, ScaleSettings
} }
/** /**
@@ -20,7 +20,7 @@ export enum PaneKind {
DeleteDatabase, DeleteDatabase,
GlobalSettings, GlobalSettings,
AdHocAccess, AdHocAccess,
SwitchDirectory, SwitchDirectory
} }
/** /**
@@ -79,5 +79,5 @@ export enum ActionType {
OpenCollectionTab, OpenCollectionTab,
OpenPane, OpenPane,
TransmitCachedData, TransmitCachedData,
OpenSampleNotebook, OpenSampleNotebook
} }

View File

@@ -56,7 +56,7 @@ export enum ApiKind {
Table, Table,
Cassandra, Cassandra,
Graph, Graph,
MongoDBCompute, MongoDBCompute
} }
export interface GenerateTokenResponse { export interface GenerateTokenResponse {
@@ -297,7 +297,7 @@ export enum AutopilotTier {
Tier1 = 1, Tier1 = 1,
Tier2 = 2, Tier2 = 2,
Tier3 = 3, Tier3 = 3,
Tier4 = 4, Tier4 = 4
} }
export interface RpOptions { export interface RpOptions {
@@ -363,7 +363,7 @@ export interface Notification {
export enum ConflictResolutionMode { export enum ConflictResolutionMode {
Custom = "Custom", Custom = "Custom",
LastWriterWins = "LastWriterWins", LastWriterWins = "LastWriterWins"
} }
/** /**
@@ -522,7 +522,7 @@ export interface SparkClusterEndpoint {
export enum SparkClusterEndpointKind { export enum SparkClusterEndpointKind {
SparkUI = "SparkUI", SparkUI = "SparkUI",
HistoryServerUI = "HistoryServerUI", HistoryServerUI = "HistoryServerUI",
Livy = "Livy", Livy = "Livy"
} }
export interface RpParameters { export interface RpParameters {
@@ -704,6 +704,49 @@ export interface MemoryUsageInfo {
totalKB: number; totalKB: number;
} }
export interface NotebookMetadata {
date: string;
description: string;
tags: string[];
author: string;
views: number;
likes: number;
downloads: number;
imageUrl: string;
}
export interface UserMetadata {
likedNotebooks: string[];
}
export interface GitHubInfoJunoResponse {
encoding: string;
encodedContent: string;
content: string;
target: string;
submoduleGitUrl: string;
name: string;
path: string;
sha: string;
size: number;
type: {
stringValue: string;
value: number;
};
downloadUrl: string;
url: string;
gitUrl: string;
htmlUrl: string;
metadata?: NotebookMetadata;
officialSamplesIndex?: number;
isLikedNotebook?: boolean;
}
export interface LikedNotebooksJunoResponse {
likedNotebooksContent: GitHubInfoJunoResponse[];
userMetadata: UserMetadata;
}
export interface resourceTokenConnectionStringProperties { export interface resourceTokenConnectionStringProperties {
accountEndpoint: string; accountEndpoint: string;
collectionId: string; collectionId: string;

View File

@@ -21,7 +21,7 @@ export enum LogEntryLevel {
/** /**
* Error level. * Error level.
*/ */
Error = 2, Error = 2
} }
/** /**
* Schema of a log entry. * Schema of a log entry.

View File

@@ -32,7 +32,7 @@ export enum MessageTypes {
GetArcadiaToken, GetArcadiaToken,
CreateWorkspace, CreateWorkspace,
CreateSparkPool, CreateSparkPool,
RefreshDatabaseAccount, RefreshDatabaseAccount
} }
export { Versions, ActionContracts, Diagnostics }; export { Versions, ActionContracts, Diagnostics };

View File

@@ -9,11 +9,14 @@ import { AccessibleVerticalList } from "../Explorer/Tree/AccessibleVerticalList"
import { ArcadiaWorkspaceItem } from "../Explorer/Controls/Arcadia/ArcadiaMenuPicker"; import { ArcadiaWorkspaceItem } from "../Explorer/Controls/Arcadia/ArcadiaMenuPicker";
import { CassandraTableKey, CassandraTableKeys, TableDataClient } from "../Explorer/Tables/TableDataClient"; import { CassandraTableKey, CassandraTableKeys, TableDataClient } from "../Explorer/Tables/TableDataClient";
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent"; import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
import { CommandButtonOptions } from "../Explorer/Controls/CommandButton/CommandButton";
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { ExecuteSprocParam } from "../Explorer/Panes/ExecuteSprocParamsPane"; import { ExecuteSprocParam } from "../Explorer/Panes/ExecuteSprocParamsPane";
import { GitHubClient } from "../GitHub/GitHubClient"; import { GitHubClient } from "../GitHub/GitHubClient";
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
import { IColumnSetting } from "../Explorer/Panes/Tables/TableColumnOptionsPane"; import { IColumnSetting } from "../Explorer/Panes/Tables/TableColumnOptionsPane";
import { JunoClient, IGalleryItem } from "../Juno/JunoClient"; import { IContentProvider } from "@nteract/core";
import { JunoClient } from "../Juno/JunoClient";
import { Library } from "./DataModels"; import { Library } from "./DataModels";
import { MostRecentActivity } from "../Explorer/MostRecentActivity/MostRecentActivity"; import { MostRecentActivity } from "../Explorer/MostRecentActivity/MostRecentActivity";
import { NotebookContentItem } from "../Explorer/Notebook/NotebookContentItem"; import { NotebookContentItem } from "../Explorer/Notebook/NotebookContentItem";
@@ -24,8 +27,6 @@ import { Splitter } from "../Common/Splitter";
import { StringInputPane } from "../Explorer/Panes/StringInputPane"; import { StringInputPane } from "../Explorer/Panes/StringInputPane";
import { TextFieldProps } from "../Explorer/Controls/DialogReactComponent/DialogComponent"; import { TextFieldProps } from "../Explorer/Controls/DialogReactComponent/DialogComponent";
import { UploadDetails } from "../workers/upload/definitions"; import { UploadDetails } from "../workers/upload/definitions";
import { UploadItemsPaneAdapter } from "../Explorer/Panes/UploadItemsPaneAdapter";
import { ReactAdapter } from "../Bindings/ReactBindingHandler";
export interface ExplorerOptions { export interface ExplorerOptions {
documentClientUtility: DocumentClientUtilityBase; documentClientUtility: DocumentClientUtilityBase;
@@ -84,10 +85,8 @@ export interface Explorer {
armEndpoint: ko.Observable<string>; armEndpoint: ko.Observable<string>;
isFeatureEnabled: (feature: string) => boolean; isFeatureEnabled: (feature: string) => boolean;
isGalleryEnabled: ko.Computed<boolean>; isGalleryEnabled: ko.Computed<boolean>;
isGalleryPublishEnabled: ko.Computed<boolean>;
isGitHubPaneEnabled: ko.Observable<boolean>; isGitHubPaneEnabled: ko.Observable<boolean>;
isPublishNotebookPaneEnabled: ko.Observable<boolean>; isGraphsEnabled: ko.Computed<boolean>;
isRightPanelV2Enabled: ko.Computed<boolean>;
canExceedMaximumValue: ko.Computed<boolean>; canExceedMaximumValue: ko.Computed<boolean>;
hasAutoPilotV2FeatureFlag: ko.Computed<boolean>; hasAutoPilotV2FeatureFlag: ko.Computed<boolean>;
isHostedDataExplorerEnabled: ko.Computed<boolean>; isHostedDataExplorerEnabled: ko.Computed<boolean>;
@@ -142,7 +141,6 @@ export interface Explorer {
executeSprocParamsPane: ExecuteSprocParamsPane; executeSprocParamsPane: ExecuteSprocParamsPane;
renewAdHocAccessPane: RenewAdHocAccessPane; renewAdHocAccessPane: RenewAdHocAccessPane;
uploadItemsPane: UploadItemsPane; uploadItemsPane: UploadItemsPane;
uploadItemsPaneAdapter: UploadItemsPaneAdapter;
loadQueryPane: LoadQueryPane; loadQueryPane: LoadQueryPane;
saveQueryPane: ContextualPane; saveQueryPane: ContextualPane;
browseQueriesPane: BrowseQueriesPane; browseQueriesPane: BrowseQueriesPane;
@@ -154,7 +152,6 @@ export interface Explorer {
libraryManagePane: ContextualPane; libraryManagePane: ContextualPane;
clusterLibraryPane: ContextualPane; clusterLibraryPane: ContextualPane;
gitHubReposPane: ContextualPane; gitHubReposPane: ContextualPane;
publishNotebookPaneAdapter: ReactAdapter;
// Facade // Facade
logConsoleData(data: ConsoleData): void; logConsoleData(data: ConsoleData): void;
@@ -226,17 +223,22 @@ export interface Explorer {
arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>; arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>;
isNotebookTabActive: ko.Computed<boolean>; isNotebookTabActive: ko.Computed<boolean>;
memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>; memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
notebookManager?: any; // This is dynamically loaded
openNotebook(notebookContentItem: NotebookContentItem): Promise<boolean>; // True if it was opened, false otherwise openNotebook(notebookContentItem: NotebookContentItem): Promise<boolean>; // True if it was opened, false otherwise
resetNotebookWorkspace(): void; resetNotebookWorkspace(): void;
importAndOpen: (path: string) => Promise<boolean>; importAndOpen: (path: string) => Promise<boolean>;
importAndOpenFromGallery: (name: string, content: string) => Promise<boolean>; importAndOpenFromGallery: (path: string, newName: string, content: any) => Promise<boolean>;
publishNotebook: (name: string, content: string) => void;
openNotebookTerminal: (kind: TerminalKind) => void; openNotebookTerminal: (kind: TerminalKind) => void;
openGallery: (notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean) => void; openGallery: () => void;
openNotebookViewer: (notebookUrl: string) => void; openNotebookViewer: (
notebookUrl: string,
notebookMetadata: DataModels.NotebookMetadata,
onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise<void>,
isLikedNotebook: boolean
) => void;
notebookWorkspaceManager: NotebookWorkspaceManager; notebookWorkspaceManager: NotebookWorkspaceManager;
sparkClusterManager: SparkClusterManager; sparkClusterManager: SparkClusterManager;
notebookContentProvider: IContentProvider;
gitHubOAuthService: GitHubOAuthService;
mostRecentActivity: MostRecentActivity; mostRecentActivity: MostRecentActivity;
initNotebooks: (databaseAccount: DataModels.DatabaseAccount) => Promise<void>; initNotebooks: (databaseAccount: DataModels.DatabaseAccount) => Promise<void>;
deleteCluster(): void; deleteCluster(): void;
@@ -335,6 +337,17 @@ export interface Button {
isSelected?: ko.Computed<boolean>; 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 { export interface NotificationConsole {
filteredConsoleData: ko.ObservableArray<ConsoleData>; filteredConsoleData: ko.ObservableArray<ConsoleData>;
isConsoleExpanded: ko.Observable<boolean>; isConsoleExpanded: ko.Observable<boolean>;
@@ -358,6 +371,7 @@ export interface TreeNode {
id: ko.Observable<string>; id: ko.Observable<string>;
database?: Database; database?: Database;
collection?: Collection; collection?: Collection;
contextMenu?: ContextMenu;
onNewQueryClick?(source: any, event: MouseEvent): void; onNewQueryClick?(source: any, event: MouseEvent): void;
onNewStoredProcedureClick?(source: Collection, event: MouseEvent): void; onNewStoredProcedureClick?(source: Collection, event: MouseEvent): void;
@@ -522,7 +536,7 @@ export interface StoredProcedure extends TreeNode {
id: ko.Observable<string>; id: ko.Observable<string>;
body: ko.Observable<string>; body: ko.Observable<string>;
delete(): void; delete(source: TreeNode, event: MouseEvent | KeyboardEvent): void;
open: () => void; open: () => void;
select(): void; select(): void;
execute(params: string[], partitionKeyValue?: string): void; execute(params: string[], partitionKeyValue?: string): void;
@@ -536,7 +550,7 @@ export interface UserDefinedFunction extends TreeNode {
id: ko.Observable<string>; id: ko.Observable<string>;
body: ko.Observable<string>; body: ko.Observable<string>;
delete(): void; delete(source: TreeNode, event: MouseEvent | KeyboardEvent): void;
open: () => void; open: () => void;
select(): void; select(): void;
} }
@@ -551,7 +565,7 @@ export interface Trigger extends TreeNode {
triggerType: ko.Observable<string>; triggerType: ko.Observable<string>;
triggerOperation: ko.Observable<string>; triggerOperation: ko.Observable<string>;
delete(): void; delete(source: TreeNode, event: MouseEvent | KeyboardEvent): void;
open: () => void; open: () => void;
select(): void; select(): void;
} }
@@ -591,16 +605,6 @@ export interface GitHubReposPaneOptions extends PaneOptions {
junoClient: JunoClient; junoClient: JunoClient;
} }
export interface PublishNotebookPaneOptions extends PaneOptions {
junoClient: JunoClient;
}
export interface PublishNotebookPaneOpenOptions {
name: string;
author: string;
content: string;
}
export interface AddCollectionPaneOptions extends PaneOptions { export interface AddCollectionPaneOptions extends PaneOptions {
isPreferredApiTable: ko.Computed<boolean>; isPreferredApiTable: ko.Computed<boolean>;
databaseId?: string; databaseId?: string;
@@ -711,7 +715,7 @@ export interface UploadFilePane extends ContextualPane {
export enum NeighborType { export enum NeighborType {
SOURCES_ONLY, SOURCES_ONLY,
TARGETS_ONLY, TARGETS_ONLY,
BOTH, BOTH
} }
/** /**
@@ -880,16 +884,16 @@ export interface TerminalTabOptions extends TabOptions {
export interface GalleryTabOptions extends TabOptions { export interface GalleryTabOptions extends TabOptions {
account: DatabaseAccount; account: DatabaseAccount;
container: Explorer; container: Explorer;
junoClient: JunoClient;
notebookUrl?: string;
galleryItem?: IGalleryItem;
isFavorite?: boolean;
} }
export interface NotebookViewerTabOptions extends TabOptions { export interface NotebookViewerTabOptions extends TabOptions {
account: DatabaseAccount; account: DatabaseAccount;
container: Explorer; container: Explorer;
notebookUrl: string; notebookUrl: string;
notebookName: string;
notebookMetadata: DataModels.NotebookMetadata;
onNotebookMetadataChange: (newNotebookMetadata: DataModels.NotebookMetadata) => Promise<void>;
isLikedNotebook: boolean;
} }
export interface DocumentsTabOptions extends TabOptions { export interface DocumentsTabOptions extends TabOptions {
@@ -1170,6 +1174,7 @@ export interface TriggerTab extends ScriptTab {
} }
export interface GraphTab extends Tab {} export interface GraphTab extends Tab {}
export interface NotebookTab extends Tab {}
export interface EditorPosition { export interface EditorPosition {
line: number; line: number;
column: number; column: number;
@@ -1183,14 +1188,14 @@ export enum DocumentExplorerState {
newDocumentInvalid, newDocumentInvalid,
exisitingDocumentNoEdits, exisitingDocumentNoEdits,
exisitingDocumentDirtyValid, exisitingDocumentDirtyValid,
exisitingDocumentDirtyInvalid, exisitingDocumentDirtyInvalid
} }
export enum IndexingPolicyEditorState { export enum IndexingPolicyEditorState {
noCollectionSelected, noCollectionSelected,
noEdits, noEdits,
dirtyValid, dirtyValid,
dirtyInvalid, dirtyInvalid
} }
export enum ScriptEditorState { export enum ScriptEditorState {
@@ -1198,7 +1203,7 @@ export enum ScriptEditorState {
newValid, newValid,
exisitingNoEdits, exisitingNoEdits,
exisitingDirtyValid, exisitingDirtyValid,
exisitingDirtyInvalid, exisitingDirtyInvalid
} }
export enum CollectionTabKind { export enum CollectionTabKind {
@@ -1213,18 +1218,29 @@ export enum CollectionTabKind {
MongoShell = 10, MongoShell = 10,
DatabaseSettings = 11, DatabaseSettings = 11,
Conflicts = 12, Conflicts = 12,
Notebook = 13 /* Deprecated */, Notebook = 13,
Terminal = 14, Terminal = 14,
NotebookV2 = 15, NotebookV2 = 15,
SparkMasterTab = 16, SparkMasterTab = 16,
Gallery = 17, Gallery = 17,
NotebookViewer = 18, NotebookViewer = 18
} }
export enum TerminalKind { export enum TerminalKind {
Default = 0, Default = 0,
Mongo = 1, Mongo = 1,
Cassandra = 2, 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 { export interface DataExplorerInputsFrame {
@@ -1276,7 +1292,7 @@ export enum SubscriptionType {
EA, EA,
Free, Free,
Internal, Internal,
PAYG, PAYG
} }
export class MonacoEditorSettings { export class MonacoEditorSettings {

View File

@@ -6,19 +6,19 @@ describe("The Heatmap Control", () => {
const dataPoints = { const dataPoints = {
"1": { "1": {
"2019-06-19T00:59:10Z": { "2019-06-19T00:59:10Z": {
"Normalized Throughput": 0.35, "Normalized Throughput": 0.35
}, },
"2019-06-19T00:48:10Z": { "2019-06-19T00:48:10Z": {
"Normalized Throughput": 0.25, "Normalized Throughput": 0.25
}, }
}, }
}; };
const chartCaptions = { const chartCaptions = {
chartTitle: "chart title", chartTitle: "chart title",
yAxisTitle: "YAxisTitle", yAxisTitle: "YAxisTitle",
tooltipText: "Tooltip text", tooltipText: "Tooltip text",
timeWindow: 123456789, timeWindow: 123456789
}; };
let heatmap: Heatmap; let heatmap: Heatmap;
@@ -75,12 +75,12 @@ describe("The Heatmap Control", () => {
if (dayjs().utcOffset()) { if (dayjs().utcOffset()) {
expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints).not.toEqual([ expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints).not.toEqual([
"2019-06-19T00:48:10Z", "2019-06-19T00:48:10Z",
"2019-06-19T00:59:10Z", "2019-06-19T00:59:10Z"
]); ]);
} else { } else {
expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints).toEqual([ expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints).toEqual([
"2019-06-19T00:48:10Z", "2019-06-19T00:48:10Z",
"2019-06-19T00:59:10Z", "2019-06-19T00:59:10Z"
]); ]);
} }
}); });
@@ -106,9 +106,9 @@ describe("iframe rendering when there is no data", () => {
data: { data: {
chartData: {}, chartData: {},
chartSettings: {}, chartSettings: {},
theme: 4, theme: 4
}, }
}, }
}; };
const divElement: string = `<div id="${Heatmap.elementId}"></div>`; const divElement: string = `<div id="${Heatmap.elementId}"></div>`;
@@ -126,9 +126,9 @@ describe("iframe rendering when there is no data", () => {
data: { data: {
chartData: {}, chartData: {},
chartSettings: {}, chartSettings: {},
theme: 2, theme: 2
}, }
}, }
}; };
const divElement: string = `<div id="${Heatmap.elementId}"></div>`; const divElement: string = `<div id="${Heatmap.elementId}"></div>`;

View File

@@ -9,7 +9,7 @@ import {
HeatmapData, HeatmapData,
LayoutSettings, LayoutSettings,
PartitionTimeStampToData, PartitionTimeStampToData,
PortalTheme, PortalTheme
} from "./HeatmapDatatypes"; } from "./HeatmapDatatypes";
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation"; import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
import { MessageHandler } from "../../Common/MessageHandler"; import { MessageHandler } from "../../Common/MessageHandler";
@@ -43,7 +43,7 @@ export class Heatmap {
return { return {
family: StyleConstants.DataExplorerFont, family: StyleConstants.DataExplorerFont,
size, size,
color, color
}; };
} }
@@ -73,7 +73,7 @@ export class Heatmap {
return 0; return 0;
} }
} }
}), })
}; };
// go thru all rows and create 2d matrix for heatmap... // go thru all rows and create 2d matrix for heatmap...
for (let i = 0; i < rows.length; i++) { for (let i = 0; i < rows.length; i++) {
@@ -115,7 +115,7 @@ export class Heatmap {
[0.7, "#E46612"], [0.7, "#E46612"],
[0.8, "#E64914"], [0.8, "#E64914"],
[0.9, "#B80016"], [0.9, "#B80016"],
[1.0, "#B80016"], [1.0, "#B80016"]
], ],
name: "", name: "",
hovertemplate: this._heatmapCaptions.tooltipText, hovertemplate: this._heatmapCaptions.tooltipText,
@@ -123,11 +123,11 @@ export class Heatmap {
thickness: 15, thickness: 15,
outlinewidth: 0, outlinewidth: 0,
tickcolor: StyleConstants.BaseDark, tickcolor: StyleConstants.BaseDark,
tickfont: this._getFontStyles(10, this._defaultFontColor), tickfont: this._getFontStyles(10, this._defaultFontColor)
}, },
y: this._chartData.yAxisPoints, y: this._chartData.yAxisPoints,
x: this._chartData.xAxisPoints, x: this._chartData.xAxisPoints
}, }
]; ];
} }
@@ -138,7 +138,7 @@ export class Heatmap {
r: 10, r: 10,
b: 35, b: 35,
t: 30, t: 30,
pad: 0, pad: 0
}, },
paper_bgcolor: "transparent", paper_bgcolor: "transparent",
plot_bgcolor: "transparent", plot_bgcolor: "transparent",
@@ -154,7 +154,7 @@ export class Heatmap {
autotick: true, autotick: true,
fixedrange: true, fixedrange: true,
ticks: "", ticks: "",
showticklabels: false, showticklabels: false
}, },
xaxis: { xaxis: {
fixedrange: true, fixedrange: true,
@@ -167,13 +167,13 @@ export class Heatmap {
autotick: true, autotick: true,
tickformat: this._heatmapCaptions.timeWindow > 7 ? "%I:%M %p" : "%b %e", tickformat: this._heatmapCaptions.timeWindow > 7 ? "%I:%M %p" : "%b %e",
showticklabels: true, showticklabels: true,
tickfont: this._getFontStyles(10), tickfont: this._getFontStyles(10)
}, },
title: { title: {
text: this._heatmapCaptions.chartTitle, text: this._heatmapCaptions.chartTitle,
x: 0.01, x: 0.01,
font: this._getFontStyles(13, this._defaultFontColor), font: this._getFontStyles(13, this._defaultFontColor)
}, }
}; };
} }
@@ -181,7 +181,7 @@ export class Heatmap {
return { return {
/* heatmap can be fully responsive however the min-height needed in that case is greater than the iframe portal height, hence explicit width + height have been set in _getLayoutSettings /* heatmap can be fully responsive however the min-height needed in that case is greater than the iframe portal height, hence explicit width + height have been set in _getLayoutSettings
responsive: true,*/ responsive: true,*/
displayModeBar: false, displayModeBar: false
}; };
} }

View File

@@ -8,7 +8,7 @@ export enum PortalTheme {
blue = 1, blue = 1,
azure, azure,
light, light,
dark, dark
} }
export interface HeatmapData { export interface HeatmapData {

View File

@@ -4,6 +4,10 @@ import * as ko from "knockout";
import "./ComponentRegisterer"; import "./ComponentRegisterer";
describe("Component Registerer", () => { describe("Component Registerer", () => {
it("should register command-button component", () => {
expect(ko.components.isRegistered("command-button")).toBe(true);
});
it("should register input-typeahead component", () => { it("should register input-typeahead component", () => {
expect(ko.components.isRegistered("input-typeahead")).toBe(true); expect(ko.components.isRegistered("input-typeahead")).toBe(true);
}); });
@@ -60,6 +64,10 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("graph-tab")).toBe(true); 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", () => { it("should register notebookv2-tab component", () => {
expect(ko.components.isRegistered("notebookv2-tab")).toBe(true); expect(ko.components.isRegistered("notebookv2-tab")).toBe(true);
}); });
@@ -76,6 +84,30 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("mongo-shell-tab")).toBe(true); 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", () => { it("should registeradd-collection-pane component", () => {
expect(ko.components.isRegistered("add-collection-pane")).toBe(true); expect(ko.components.isRegistered("add-collection-pane")).toBe(true);
}); });
@@ -124,6 +156,10 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("manage-spark-cluster-pane")).toBe(true); 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", () => { it("should register dynamic-list component", () => {
expect(ko.components.isRegistered("dynamic-list")).toBe(true); expect(ko.components.isRegistered("dynamic-list")).toBe(true);
}); });

View File

@@ -1,7 +1,9 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as PaneComponents from "./Panes/PaneComponents"; import * as PaneComponents from "./Panes/PaneComponents";
import * as TabComponents from "./Tabs/TabComponents"; import * as TabComponents from "./Tabs/TabComponents";
import * as TreeComponents from "./Tree/TreeComponents";
import { CollapsiblePanelComponent } from "./Controls/CollapsiblePanel/CollapsiblePanelComponent"; import { CollapsiblePanelComponent } from "./Controls/CollapsiblePanel/CollapsiblePanelComponent";
import { CommandButtonComponent } from "./Controls/CommandButton/CommandButton";
import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent"; import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent";
import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent"; import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent";
import { EditorComponent } from "./Controls/Editor/EditorComponent"; import { EditorComponent } from "./Controls/Editor/EditorComponent";
@@ -14,6 +16,7 @@ import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputI
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3"; import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
import { ToolbarComponent } from "./Controls/Toolbar/Toolbar"; import { ToolbarComponent } from "./Controls/Toolbar/Toolbar";
ko.components.register("command-button", CommandButtonComponent);
ko.components.register("toolbar", new ToolbarComponent()); ko.components.register("toolbar", new ToolbarComponent());
ko.components.register("input-typeahead", new InputTypeaheadComponent()); ko.components.register("input-typeahead", new InputTypeaheadComponent());
ko.components.register("new-vertex-form", NewVertexComponent); ko.components.register("new-vertex-form", NewVertexComponent);
@@ -39,6 +42,7 @@ ko.components.register("tables-query-tab", new TabComponents.QueryTablesTab());
ko.components.register("graph-tab", new TabComponents.GraphTab()); ko.components.register("graph-tab", new TabComponents.GraphTab());
ko.components.register("mongo-shell-tab", new TabComponents.MongoShellTab()); ko.components.register("mongo-shell-tab", new TabComponents.MongoShellTab());
ko.components.register("conflicts-tab", new TabComponents.ConflictsTab()); 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("notebookv2-tab", new TabComponents.NotebookV2Tab());
ko.components.register("terminal-tab", new TabComponents.TerminalTab()); ko.components.register("terminal-tab", new TabComponents.TerminalTab());
ko.components.register("spark-master-tab", new TabComponents.SparkMasterTab()); ko.components.register("spark-master-tab", new TabComponents.SparkMasterTab());
@@ -48,6 +52,14 @@ ko.components.register("notebook-viewer-tab", new TabComponents.NotebookViewerTa
// Database Tabs // Database Tabs
ko.components.register("database-settings-tab", new TabComponents.DatabaseSettingsTab()); 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 // Panes
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent()); ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent()); ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent());
@@ -81,3 +93,6 @@ ko.components.register("manage-spark-cluster-pane", new PaneComponents.ManageSpa
ko.components.register("library-manage-pane", new PaneComponents.LibraryManagePaneComponent()); ko.components.register("library-manage-pane", new PaneComponents.LibraryManagePaneComponent());
ko.components.register("cluster-library-pane", new PaneComponents.ClusterLibraryPaneComponent()); ko.components.register("cluster-library-pane", new PaneComponents.ClusterLibraryPaneComponent());
ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent()); ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent());
// Menus
ko.components.register("collection-node-context-menu", new TreeComponents.CollectionTreeNodeContextMenu());

View File

@@ -1,5 +1,6 @@
import * as ko from "knockout"; import * as ko from "knockout";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { CommandButtonOptions } from "./Controls/CommandButton/CommandButton";
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent"; import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
import AddCollectionIcon from "../../images/AddCollection.svg"; import AddCollectionIcon from "../../images/AddCollection.svg";
import AddSqlQueryIcon from "../../images/AddSqlQuery_16x16.svg"; import AddSqlQueryIcon from "../../images/AddSqlQuery_16x16.svg";
@@ -32,13 +33,13 @@ export class ResourceTreeContextMenuButtonFactory {
const newCollectionMenuItem: TreeNodeMenuItem = { const newCollectionMenuItem: TreeNodeMenuItem = {
iconSrc: AddCollectionIcon, iconSrc: AddCollectionIcon,
onClick: () => container.onNewCollectionClicked(), onClick: () => container.onNewCollectionClicked(),
label: container.addCollectionText(), label: container.addCollectionText()
}; };
const deleteDatabaseMenuItem = { const deleteDatabaseMenuItem = {
iconSrc: DeleteDatabaseIcon, iconSrc: DeleteDatabaseIcon,
onClick: () => container.deleteDatabaseConfirmationPane.open(), onClick: () => container.deleteDatabaseConfirmationPane.open(),
label: container.deleteDatabaseText(), label: container.deleteDatabaseText()
}; };
return [newCollectionMenuItem, deleteDatabaseMenuItem]; return [newCollectionMenuItem, deleteDatabaseMenuItem];
} }
@@ -52,7 +53,7 @@ export class ResourceTreeContextMenuButtonFactory {
items.push({ items.push({
iconSrc: AddSqlQueryIcon, iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null), onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null),
label: "New SQL Query", label: "New SQL Query"
}); });
} }
@@ -60,7 +61,7 @@ export class ResourceTreeContextMenuButtonFactory {
items.push({ items.push({
iconSrc: AddSqlQueryIcon, iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null), onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null),
label: "New Query", label: "New Query"
}); });
items.push({ items.push({
@@ -69,7 +70,7 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewMongoShellClick(); selectedCollection && selectedCollection.onNewMongoShellClick();
}, },
label: "New Shell", label: "New Shell"
}); });
} }
@@ -80,7 +81,7 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null); selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
}, },
label: "New Stored Procedure", label: "New Stored Procedure"
}); });
items.push({ items.push({
@@ -89,7 +90,7 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, null); selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, null);
}, },
label: "New UDF", label: "New UDF"
}); });
items.push({ items.push({
@@ -98,7 +99,7 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, null); selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, null);
}, },
label: "New Trigger", label: "New Trigger"
}); });
} }
@@ -108,16 +109,13 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection(); const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onDeleteCollectionContextMenuClick(selectedCollection, null); selectedCollection && selectedCollection.onDeleteCollectionContextMenuClick(selectedCollection, null);
}, },
label: container.deleteCollectionText(), label: container.deleteCollectionText()
}); });
return items; return items;
} }
public static createStoreProcedureContextMenuItems( public static createStoreProcedureContextMenuItems(container: ViewModels.Explorer): TreeNodeMenuItem[] {
container: ViewModels.Explorer,
storedProcedure: ViewModels.StoredProcedure
): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) { if (container.isPreferredApiCassandra()) {
return []; return [];
} }
@@ -125,16 +123,16 @@ export class ResourceTreeContextMenuButtonFactory {
return [ return [
{ {
iconSrc: DeleteSprocIcon, iconSrc: DeleteSprocIcon,
onClick: () => storedProcedure.delete(), onClick: () => {
label: "Delete Store Procedure", const selectedStoreProcedure: ViewModels.StoredProcedure = container.findSelectedStoredProcedure();
}, selectedStoreProcedure && selectedStoreProcedure.delete(selectedStoreProcedure, null);
},
label: "Delete Store Procedure"
}
]; ];
} }
public static createTriggerContextMenuItems( public static createTriggerContextMenuItems(container: ViewModels.Explorer): TreeNodeMenuItem[] {
container: ViewModels.Explorer,
trigger: ViewModels.Trigger
): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) { if (container.isPreferredApiCassandra()) {
return []; return [];
} }
@@ -142,16 +140,16 @@ export class ResourceTreeContextMenuButtonFactory {
return [ return [
{ {
iconSrc: DeleteTriggerIcon, iconSrc: DeleteTriggerIcon,
onClick: () => trigger.delete(), onClick: () => {
label: "Delete Trigger", const selectedTrigger: ViewModels.Trigger = container.findSelectedTrigger();
}, selectedTrigger && selectedTrigger.delete(selectedTrigger, null);
},
label: "Delete Trigger"
}
]; ];
} }
public static createUserDefinedFunctionContextMenuItems( public static createUserDefinedFunctionContextMenuItems(container: ViewModels.Explorer): TreeNodeMenuItem[] {
container: ViewModels.Explorer,
userDefinedFunction: ViewModels.UserDefinedFunction
): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) { if (container.isPreferredApiCassandra()) {
return []; return [];
} }
@@ -159,9 +157,266 @@ export class ResourceTreeContextMenuButtonFactory {
return [ return [
{ {
iconSrc: DeleteUDFIcon, iconSrc: DeleteUDFIcon,
onClick: () => userDefinedFunction.delete(), onClick: () => {
label: "Delete User Defined Function", const selectedUDF: ViewModels.UserDefinedFunction = container.findSelectedUDF();
}, selectedUDF && selectedUDF.delete(selectedUDF, null);
},
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];
}
}

View File

@@ -31,7 +31,7 @@ export class AccessibleElement extends React.Component<AccessibleElementProps> {
...elementProps, ...elementProps,
onKeyPress: this.onKeyPress, onKeyPress: this.onKeyPress,
onClick: this.props.onActivated, onClick: this.props.onActivated,
tabIndex, tabIndex
}); });
} }
} }

View File

@@ -38,7 +38,7 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
super(props); super(props);
this.isExpanded = props.isExpanded; this.isExpanded = props.isExpanded;
this.state = { this.state = {
isExpanded: true, isExpanded: true
}; };
} }
@@ -46,7 +46,7 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
if (this.props.isExpanded !== this.isExpanded) { if (this.props.isExpanded !== this.isExpanded) {
this.isExpanded = this.props.isExpanded; this.isExpanded = this.props.isExpanded;
this.setState({ this.setState({
isExpanded: this.props.isExpanded, isExpanded: this.props.isExpanded
}); });
} }
} }

View File

@@ -16,7 +16,7 @@ const createBlankProps = (): AccountSwitchComponentProps => {
subscriptions: [], subscriptions: [],
selectedSubscriptionId: null, selectedSubscriptionId: null,
isLoadingSubscriptions: false, isLoadingSubscriptions: false,
onSubscriptionChange: jest.fn(), onSubscriptionChange: jest.fn()
}; };
}; };
@@ -28,7 +28,7 @@ const createBlankAccount = (): DatabaseAccount => {
properties: null, properties: null,
location: "", location: "",
tags: null, tags: null,
type: "", type: ""
}; };
}; };
@@ -40,7 +40,7 @@ const createBlankSubscription = (): Subscription => {
state: "", state: "",
subscriptionPolicies: null, subscriptionPolicies: null,
tenantId: "", tenantId: "",
uniqueDisplayName: "", uniqueDisplayName: ""
}; };
}; };

View File

@@ -34,13 +34,13 @@ export class AccountSwitchComponent extends React.Component<AccountSwitchCompone
items: [ items: [
{ {
key: "switchSubscription", key: "switchSubscription",
onRender: this._renderSubscriptionDropdown.bind(this), onRender: this._renderSubscriptionDropdown.bind(this)
}, },
{ {
key: "switchAccount", key: "switchAccount",
onRender: this._renderAccountDropDown.bind(this), onRender: this._renderAccountDropDown.bind(this)
}, }
], ]
}; };
const buttonStyles: IButtonStyles = { const buttonStyles: IButtonStyles = {
@@ -51,27 +51,27 @@ export class AccountSwitchComponent extends React.Component<AccountSwitchCompone
paddingLeft: 10, paddingLeft: 10,
marginRight: 5, marginRight: 5,
backgroundColor: StyleConstants.BaseDark, backgroundColor: StyleConstants.BaseDark,
color: StyleConstants.BaseLight, color: StyleConstants.BaseLight
}, },
rootHovered: { rootHovered: {
backgroundColor: StyleConstants.BaseHigh, backgroundColor: StyleConstants.BaseHigh,
color: StyleConstants.BaseLight, color: StyleConstants.BaseLight
}, },
rootFocused: { rootFocused: {
backgroundColor: StyleConstants.BaseHigh, backgroundColor: StyleConstants.BaseHigh,
color: StyleConstants.BaseLight, color: StyleConstants.BaseLight
}, },
rootPressed: { rootPressed: {
backgroundColor: StyleConstants.BaseHigh, backgroundColor: StyleConstants.BaseHigh,
color: StyleConstants.BaseLight, color: StyleConstants.BaseLight
}, },
rootExpanded: { rootExpanded: {
backgroundColor: StyleConstants.BaseHigh, backgroundColor: StyleConstants.BaseHigh,
color: StyleConstants.BaseLight, color: StyleConstants.BaseLight
}, },
textContainer: { textContainer: {
flexGrow: "initial", flexGrow: "initial"
}, }
}; };
const buttonProps: IButtonProps = { const buttonProps: IButtonProps = {
@@ -79,7 +79,7 @@ export class AccountSwitchComponent extends React.Component<AccountSwitchCompone
menuProps: menuProps, menuProps: menuProps,
styles: buttonStyles, styles: buttonStyles,
className: "accountSwitchButton", className: "accountSwitchButton",
id: "accountSwitchButton", id: "accountSwitchButton"
}; };
return <DefaultButton {...buttonProps} />; return <DefaultButton {...buttonProps} />;
@@ -87,11 +87,11 @@ export class AccountSwitchComponent extends React.Component<AccountSwitchCompone
private _renderSubscriptionDropdown(): JSX.Element { private _renderSubscriptionDropdown(): JSX.Element {
const { subscriptions, selectedSubscriptionId, isLoadingSubscriptions } = this.props; const { subscriptions, selectedSubscriptionId, isLoadingSubscriptions } = this.props;
const options: IDropdownOption[] = subscriptions.map((sub) => { const options: IDropdownOption[] = subscriptions.map(sub => {
return { return {
key: sub.subscriptionId, key: sub.subscriptionId,
text: sub.displayName, text: sub.displayName,
data: sub, data: sub
}; };
}); });
@@ -109,8 +109,8 @@ export class AccountSwitchComponent extends React.Component<AccountSwitchCompone
defaultSelectedKey: selectedSubscriptionId, defaultSelectedKey: selectedSubscriptionId,
placeholder: placeHolderText, placeholder: placeHolderText,
styles: { styles: {
callout: "accountSwitchSubscriptionDropdownMenu", callout: "accountSwitchSubscriptionDropdownMenu"
}, }
}; };
return <Dropdown {...dropdownProps} />; return <Dropdown {...dropdownProps} />;
@@ -126,11 +126,11 @@ export class AccountSwitchComponent extends React.Component<AccountSwitchCompone
private _renderAccountDropDown(): JSX.Element { private _renderAccountDropDown(): JSX.Element {
const { accounts, selectedAccountName, isLoadingAccounts } = this.props; const { accounts, selectedAccountName, isLoadingAccounts } = this.props;
const options: IDropdownOption[] = accounts.map((account) => { const options: IDropdownOption[] = accounts.map(account => {
return { return {
key: account.name, key: account.name,
text: account.name, text: account.name,
data: account, data: account
}; };
}); });
// Fabric UI will also try to select the first non-disabled option from dropdown. // Fabric UI will also try to select the first non-disabled option from dropdown.
@@ -138,7 +138,7 @@ export class AccountSwitchComponent extends React.Component<AccountSwitchCompone
options.unshift({ options.unshift({
key: "select from list", key: "select from list",
text: "Select Cosmos DB account from list", text: "Select Cosmos DB account from list",
data: undefined, data: undefined
}); });
const placeHolderText = isLoadingAccounts const placeHolderText = isLoadingAccounts
@@ -155,8 +155,8 @@ export class AccountSwitchComponent extends React.Component<AccountSwitchCompone
defaultSelectedKey: selectedAccountName, defaultSelectedKey: selectedAccountName,
placeholder: placeHolderText, placeholder: placeHolderText,
styles: { styles: {
callout: "accountSwitchAccountDropdownMenu", callout: "accountSwitchAccountDropdownMenu"
}, }
}; };
return <Dropdown {...dropdownProps} />; return <Dropdown {...dropdownProps} />;

View File

@@ -4,9 +4,9 @@ import { DefaultButton, IButtonStyles } from "office-ui-fabric-react/lib/Button"
import { import {
IContextualMenuItem, IContextualMenuItem,
IContextualMenuProps, IContextualMenuProps,
ContextualMenuItemType, ContextualMenuItemType
} from "office-ui-fabric-react/lib/ContextualMenu"; } from "office-ui-fabric-react/lib/ContextualMenu";
import * as Logger from "../../../Common/Logger"; import { Logger } from "../../../Common/Logger";
export interface ArcadiaMenuPickerProps { export interface ArcadiaMenuPickerProps {
selectText?: string; selectText?: string;
@@ -33,7 +33,7 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
constructor(props: ArcadiaMenuPickerProps) { constructor(props: ArcadiaMenuPickerProps) {
super(props); super(props);
this.state = { this.state = {
selectedSparkPool: props.selectedSparkPool, selectedSparkPool: props.selectedSparkPool
}; };
} }
@@ -44,7 +44,7 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
try { try {
this.props.onSparkPoolSelect(e, item); this.props.onSparkPoolSelect(e, item);
this.setState({ this.setState({
selectedSparkPool: item.text, selectedSparkPool: item.text
}); });
} catch (error) { } catch (error) {
Logger.logError(error, "ArcadiaMenuPicker/_onSparkPoolClicked"); Logger.logError(error, "ArcadiaMenuPicker/_onSparkPoolClicked");
@@ -68,28 +68,28 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
public render() { public render() {
const { workspaces } = this.props; const { workspaces } = this.props;
let workspaceMenuItems: IContextualMenuItem[] = workspaces.map((workspace) => { let workspaceMenuItems: IContextualMenuItem[] = workspaces.map(workspace => {
let sparkPoolsMenuProps: IContextualMenuProps = { let sparkPoolsMenuProps: IContextualMenuProps = {
items: workspace.sparkPools.map( items: workspace.sparkPools.map(
(sparkpool): IContextualMenuItem => ({ (sparkpool): IContextualMenuItem => ({
key: sparkpool.id, key: sparkpool.id,
text: sparkpool.name, text: sparkpool.name,
onClick: this._onSparkPoolClicked, onClick: this._onSparkPoolClicked
}) })
), )
}; };
if (!sparkPoolsMenuProps.items.length) { if (!sparkPoolsMenuProps.items.length) {
sparkPoolsMenuProps.items.push({ sparkPoolsMenuProps.items.push({
key: workspace.id, key: workspace.id,
text: "Create new spark pool", text: "Create new spark pool",
onClick: this._onCreateNewSparkPoolClicked, onClick: this._onCreateNewSparkPoolClicked
}); });
} }
return { return {
key: workspace.id, key: workspace.id,
text: workspace.name, text: workspace.name,
subMenuProps: this.props.disableSubmenu ? undefined : sparkPoolsMenuProps, subMenuProps: this.props.disableSubmenu ? undefined : sparkPoolsMenuProps
}; };
}); });
@@ -97,7 +97,7 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
workspaceMenuItems.push({ workspaceMenuItems.push({
key: "create_workspace", key: "create_workspace",
text: "Create new workspace", text: "Create new workspace",
onClick: this._onCreateNewWorkspaceClicked, onClick: this._onCreateNewWorkspaceClicked
}); });
} }
@@ -106,29 +106,29 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
backgroundColor: "transparent", backgroundColor: "transparent",
margin: "auto 5px", margin: "auto 5px",
padding: "0", padding: "0",
border: "0", border: "0"
}, },
rootHovered: { rootHovered: {
backgroundColor: "transparent", backgroundColor: "transparent"
}, },
rootChecked: { rootChecked: {
backgroundColor: "transparent", backgroundColor: "transparent"
}, },
rootFocused: { rootFocused: {
backgroundColor: "transparent", backgroundColor: "transparent"
}, },
rootExpanded: { rootExpanded: {
backgroundColor: "transparent", backgroundColor: "transparent"
}, },
flexContainer: { flexContainer: {
height: "30px", height: "30px",
border: "1px solid #a6a6a6", border: "1px solid #a6a6a6",
padding: "0 8px", padding: "0 8px"
}, },
label: { label: {
fontWeight: "400", fontWeight: "400",
fontSize: "12px", fontSize: "12px"
}, }
}; };
return ( return (
@@ -137,7 +137,7 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
persistMenu={true} persistMenu={true}
className="arcadia-menu-picker" className="arcadia-menu-picker"
menuProps={{ menuProps={{
items: workspaceMenuItems, items: workspaceMenuItems
}} }}
styles={dropdownStyle} styles={dropdownStyle}
/> />

View File

@@ -8,7 +8,7 @@ export class CollapsiblePanelComponent {
constructor() { constructor() {
return { return {
viewModel: CollapsiblePanelViewModel, viewModel: CollapsiblePanelViewModel,
template, template
}; };
} }
} }

View File

@@ -0,0 +1,200 @@
@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;
}

View File

@@ -0,0 +1,139 @@
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);
});
});
});

View File

@@ -0,0 +1,191 @@
/**
* 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
};

View File

@@ -15,20 +15,15 @@ import { ArcadiaMenuPickerProps } from "../Arcadia/ArcadiaMenuPicker";
* Options for this component * Options for this component
*/ */
export interface CommandButtonComponentProps { export interface CommandButtonComponentProps {
/**
* font icon name for the button
*/
iconName?: string;
/** /**
* image source for the button icon * image source for the button icon
*/ */
iconSrc?: string; iconSrc: string;
/** /**
* image alt for accessibility * image alt for accessibility
*/ */
iconAlt?: string; iconAlt: string;
/** /**
* Click handler for command button click * Click handler for command button click
@@ -149,7 +144,9 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
private onLauncherKeyDown(event: React.KeyboardEvent<HTMLDivElement>): boolean { private onLauncherKeyDown(event: React.KeyboardEvent<HTMLDivElement>): boolean {
if (event.keyCode === KeyCodes.DownArrow) { if (event.keyCode === KeyCodes.DownArrow) {
$(this.dropdownElt).hide(); $(this.dropdownElt).hide();
$(this.dropdownElt).show().focus(); $(this.dropdownElt)
.show()
.focus();
event.stopPropagation(); event.stopPropagation();
return false; return false;
} }
@@ -185,7 +182,7 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
} }
this.props.onCommandClick(e); this.props.onCommandClick(e);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
commandButtonClicked: this.props.commandButtonLabel, commandButtonClicked: this.props.commandButtonLabel
}); });
} }

View File

@@ -0,0 +1,40 @@
<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 -->

View File

@@ -53,26 +53,26 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
subText: this.props.subText, subText: this.props.subText,
styles: { styles: {
title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT }, title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT },
subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE }, subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE }
}, },
showCloseButton: false, showCloseButton: false
}, },
modalProps: { isBlocking: this.props.isModal }, modalProps: { isBlocking: this.props.isModal },
minWidth: DIALOG_MIN_WIDTH, minWidth: DIALOG_MIN_WIDTH,
maxWidth: DIALOG_MAX_WIDTH, maxWidth: DIALOG_MAX_WIDTH
}; };
const textFieldProps: ITextFieldProps = this.props.textFieldProps; const textFieldProps: ITextFieldProps = this.props.textFieldProps;
const linkProps: LinkProps = this.props.linkProps; const linkProps: LinkProps = this.props.linkProps;
const primaryButtonProps: IButtonProps = { const primaryButtonProps: IButtonProps = {
text: this.props.primaryButtonText, text: this.props.primaryButtonText,
disabled: this.props.primaryButtonDisabled || false, disabled: this.props.primaryButtonDisabled || false,
onClick: this.props.onPrimaryButtonClick, onClick: this.props.onPrimaryButtonClick
}; };
const secondaryButtonProps: IButtonProps = const secondaryButtonProps: IButtonProps =
this.props.secondaryButtonText && this.props.onSecondaryButtonClick this.props.secondaryButtonText && this.props.onSecondaryButtonClick
? { ? {
text: this.props.secondaryButtonText, text: this.props.secondaryButtonText,
onClick: this.props.onSecondaryButtonClick, onClick: this.props.onSecondaryButtonClick
} }
: undefined; : undefined;

View File

@@ -9,7 +9,7 @@ export class DiffEditorComponent {
constructor() { constructor() {
return { return {
viewModel: DiffEditorViewModel, viewModel: DiffEditorViewModel,
template, template
}; };
} }
} }
@@ -103,7 +103,7 @@ export class DiffEditorViewModel {
lineNumbers: this.params.lineNumbers || "off", lineNumbers: this.params.lineNumbers || "off",
fontSize: 12, fontSize: 12,
ariaLabel: this.params.ariaLabel, ariaLabel: this.params.ariaLabel,
theme: this.params.theme, theme: this.params.theme
}; };
if (this.params.renderSideBySide !== undefined) { if (this.params.renderSideBySide !== undefined) {
@@ -120,7 +120,7 @@ export class DiffEditorViewModel {
); );
diffEditor.setModel({ diffEditor.setModel({
original: originalModel, original: originalModel,
modified: modifiedModel, modified: modifiedModel
}); });
createCallback(diffEditor); createCallback(diffEditor);
@@ -147,7 +147,7 @@ export class DiffEditorViewModel {
this.observer.observe(document.body, { this.observer.observe(document.body, {
attributes: true, attributes: true,
subtree: true, subtree: true,
childList: true, childList: true
}); });
this.editor.focus(); this.editor.focus();
} }

View File

@@ -7,7 +7,7 @@ const createBlankProps = (): DefaultDirectoryDropdownProps => {
return { return {
defaultDirectoryId: "", defaultDirectoryId: "",
directories: [], directories: [],
onDefaultDirectoryChange: jest.fn(), onDefaultDirectoryChange: jest.fn()
}; };
}; };
@@ -17,7 +17,7 @@ const createBlankDirectory = (): Tenant => {
displayName: "", displayName: "",
domains: [], domains: [],
id: "", id: "",
tenantId: "", tenantId: ""
}; };
}; };
@@ -90,15 +90,27 @@ describe("test function", () => {
const wrapper = mount(<DefaultDirectoryDropdownComponent {...props} />); const wrapper = mount(<DefaultDirectoryDropdownComponent {...props} />);
wrapper.find("div.defaultDirectoryDropdown").find("div.ms-Dropdown").simulate("click"); wrapper
.find("div.defaultDirectoryDropdown")
.find("div.ms-Dropdown")
.simulate("click");
expect(wrapper.exists("div.ms-Callout-main")).toBe(true); expect(wrapper.exists("div.ms-Callout-main")).toBe(true);
wrapper.find("button.ms-Dropdown-item").at(1).simulate("click"); wrapper
.find("button.ms-Dropdown-item")
.at(1)
.simulate("click");
expect(props.onDefaultDirectoryChange).toBeCalled(); expect(props.onDefaultDirectoryChange).toBeCalled();
expect(props.onDefaultDirectoryChange).toHaveBeenCalled(); expect(props.onDefaultDirectoryChange).toHaveBeenCalled();
wrapper.find("div.defaultDirectoryDropdown").find("div.ms-Dropdown").simulate("click"); wrapper
.find("div.defaultDirectoryDropdown")
.find("div.ms-Dropdown")
.simulate("click");
expect(wrapper.exists("div.ms-Callout-main")).toBe(true); expect(wrapper.exists("div.ms-Callout-main")).toBe(true);
wrapper.find("button.ms-Dropdown-item").at(0).simulate("click"); wrapper
.find("button.ms-Dropdown-item")
.at(0)
.simulate("click");
expect(props.onDefaultDirectoryChange).toBeCalled(); expect(props.onDefaultDirectoryChange).toBeCalled();
expect(props.onDefaultDirectoryChange).toHaveBeenCalled(); expect(props.onDefaultDirectoryChange).toHaveBeenCalled();
}); });

View File

@@ -19,13 +19,13 @@ export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDi
public render(): JSX.Element { public render(): JSX.Element {
const lastVisitedOption: IDropdownOption = { const lastVisitedOption: IDropdownOption = {
key: DefaultDirectoryDropdownComponent.lastVisitedKey, key: DefaultDirectoryDropdownComponent.lastVisitedKey,
text: "Sign in to your last visited directory", text: "Sign in to your last visited directory"
}; };
const directoryOptions: Array<IDropdownOption> = this.props.directories.map( const directoryOptions: Array<IDropdownOption> = this.props.directories.map(
(dirc): IDropdownOption => { (dirc): IDropdownOption => {
return { return {
key: dirc.tenantId, key: dirc.tenantId,
text: `${dirc.displayName}(${dirc.tenantId})`, text: `${dirc.displayName}(${dirc.tenantId})`
}; };
} }
); );
@@ -35,7 +35,7 @@ export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDi
options: dropDownOptions, options: dropDownOptions,
defaultSelectedKey: this.props.defaultDirectoryId ? this.props.defaultDirectoryId : lastVisitedOption.key, defaultSelectedKey: this.props.defaultDirectoryId ? this.props.defaultDirectoryId : lastVisitedOption.key,
onChange: this._onDropdownChange, onChange: this._onDropdownChange,
className: "defaultDirectoryDropdown", className: "defaultDirectoryDropdown"
}; };
return <Dropdown {...dropDownProps} />; return <Dropdown {...dropDownProps} />;
@@ -56,12 +56,12 @@ export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDi
countryCode: undefined, countryCode: undefined,
displayName: undefined, displayName: undefined,
domains: [], domains: [],
id: undefined, id: undefined
}); });
return; return;
} }
const selectedDirectory = _.find(this.props.directories, (d) => d.tenantId === option.key); const selectedDirectory = _.find(this.props.directories, d => d.tenantId === option.key);
if (!selectedDirectory) { if (!selectedDirectory) {
return; return;
} }

View File

@@ -7,7 +7,7 @@ const createBlankProps = (): DirectoryListProps => {
return { return {
selectedDirectoryId: undefined, selectedDirectoryId: undefined,
directories: [], directories: [],
onNewDirectorySelected: jest.fn(), onNewDirectorySelected: jest.fn()
}; };
}; };
@@ -17,7 +17,7 @@ const createBlankDirectory = (): Tenant => {
displayName: undefined, displayName: undefined,
domains: [], domains: [],
id: undefined, id: undefined,
tenantId: undefined, tenantId: undefined
}; };
}; };

View File

@@ -28,7 +28,7 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
super(props); super(props);
this.state = { this.state = {
filterText: "", filterText: ""
}; };
} }
@@ -38,12 +38,12 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
const filteredItems = const filteredItems =
originalItems && originalItems.length && filterText originalItems && originalItems.length && filterText
? originalItems.filter( ? originalItems.filter(
(directory) => directory =>
directory.displayName && directory.displayName &&
directory.displayName.toLowerCase().indexOf(filterText && filterText.toLowerCase()) >= 0 directory.displayName.toLowerCase().indexOf(filterText && filterText.toLowerCase()) >= 0
) )
: originalItems; : originalItems;
const filteredItemsSelected = filteredItems.map((t) => { const filteredItemsSelected = filteredItems.map(t => {
let tenant: ListTenant = t; let tenant: ListTenant = t;
tenant.selected = t.tenantId === selectedDirectoryId; tenant.selected = t.tenantId === selectedDirectoryId;
return tenant; return tenant;
@@ -53,7 +53,7 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
className: "directoryListFilterTextBox", className: "directoryListFilterTextBox",
placeholder: "Filter by directory name", placeholder: "Filter by directory name",
onChange: this._onFilterChanged, onChange: this._onFilterChanged,
ariaLabel: "Directory filter text box", ariaLabel: "Directory filter text box"
}; };
// TODO: add magnify glass to search bar with onRenderSuffix // TODO: add magnify glass to search bar with onRenderSuffix
@@ -69,7 +69,7 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
private _onFilterChanged = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, text?: string): void => { private _onFilterChanged = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, text?: string): void => {
this.setState({ this.setState({
filterText: text, filterText: text
}); });
}; };
@@ -84,19 +84,19 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
height: "auto", height: "auto",
borderBottom: "1px solid #ccc", borderBottom: "1px solid #ccc",
padding: "1px 0", padding: "1px 0",
width: "100%", width: "100%"
}, },
rootDisabled: { rootDisabled: {
backgroundColor: "#f1f1f8", backgroundColor: "#f1f1f8"
}, },
rootHovered: { rootHovered: {
backgroundColor: "rgba(85,179,255,.1)", backgroundColor: "rgba(85,179,255,.1)"
}, },
flexContainer: { flexContainer: {
height: "auto", height: "auto",
justifyContent: "flex-start", justifyContent: "flex-start"
}, }
}, }
}; };
return ( return (
@@ -115,7 +115,7 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
} }
const buttonElement = e.currentTarget; const buttonElement = e.currentTarget;
const selectedDirectoryId = buttonElement.getElementsByClassName("directoryListItemId")[0].textContent; const selectedDirectoryId = buttonElement.getElementsByClassName("directoryListItemId")[0].textContent;
const selectedDirectory = _.find(this.props.directories, (d) => d.tenantId === selectedDirectoryId); const selectedDirectory = _.find(this.props.directories, d => d.tenantId === selectedDirectoryId);
this.props.onNewDirectorySelected(selectedDirectory); this.props.onNewDirectorySelected(selectedDirectory);
}; };

View File

@@ -25,7 +25,7 @@ describe("Dynamic List Component", () => {
placeholder: placeholder, placeholder: placeholder,
listItems: items, listItems: items,
buttonText: mockButton, buttonText: mockButton,
ariaLabel: mockAriaLabel, ariaLabel: mockAriaLabel
}; };
} }

View File

@@ -112,5 +112,5 @@ export class DynamicListViewModel extends WaitsForTemplateViewModel {
*/ */
export const DynamicListComponent = { export const DynamicListComponent = {
viewModel: DynamicListViewModel, viewModel: DynamicListViewModel,
template, template
}; };

View File

@@ -10,7 +10,7 @@ export class EditorComponent {
constructor() { constructor() {
return { return {
viewModel: EditorViewModel, viewModel: EditorViewModel,
template, template
}; };
} }
} }

View File

@@ -70,7 +70,7 @@ export class EditorReact extends React.Component<EditorReactProps> {
fontSize: 12, fontSize: 12,
ariaLabel: this.props.ariaLabel, ariaLabel: this.props.ariaLabel,
theme: this.props.theme, theme: this.props.theme,
automaticLayout: true, automaticLayout: true
}; };
this.rootNode.innerHTML = ""; this.rootNode.innerHTML = "";

View File

@@ -10,7 +10,7 @@ export class ErrorDisplayComponent {
constructor() { constructor() {
return { return {
viewModel: ErrorDisplayViewModel, viewModel: ErrorDisplayViewModel,
template, template
}; };
} }
} }

View File

@@ -1,6 +1,6 @@
<div class="warningErrorContainer" data-bind="visible: !!params.errorMsg()"> <div class="warningErrorContainer" data-bind="visible: !!params.errorMsg()">
<div class="warningErrorContent"> <div class="warningErrorContent">
<span><img src="/error_red.svg" alt="Error" /></span> <span><img src="/error_red.svg" alt="Error"/></span>
<span class="settingErrorMsg warningErrorDetailsLinkContainer" data-bind="text: params.errorMsg()"></span> <span class="settingErrorMsg warningErrorDetailsLinkContainer" data-bind="text: params.errorMsg()"></span>
</div> </div>
</div> </div>

View File

@@ -1,20 +0,0 @@
.featurePanelComponentContainer {
width: 800px;
.urlContainer {
padding: 10px;
margin-bottom: 10px;
white-space: nowrap;
overflow: auto;
}
.options {
padding: 10px;
overflow: auto;
height: 100%;
}
.checkboxRow {
width: 390px;
}
}

View File

@@ -1,11 +0,0 @@
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();
});
});

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