Compare commits

..

32 Commits

Author SHA1 Message Date
Steve Faulkner
1636f20978 Update to Prettier 2.0 2020-07-01 23:33:09 -05:00
Steve Faulkner
9a95c7d069 Update @azure/cosmos SDK to 3.7.4 (#67) 2020-06-30 18:32:19 -05:00
Steve Faulkner
ec07ff05a4 Bundle config.json with published nugets (#64)
Co-authored-by: Vignesh Rangaishenvi <virangai@microsoft.com>
Co-authored-by: Tanuj Mittal <tamitta@microsoft.com>
2020-06-30 13:49:14 -05:00
Tanuj Mittal
7512b3c1d5 Notebooks Gallery (#59)
* Initial commit

* Address PR comments

* Move notebook related stuff to NotebookManager and dynamically load it

* Add New gallery callout and other UI tweaks

* Update test snapshot
2020-06-30 11:47:21 -07:00
vchske
dd199e6565 Fixing errors in mongo document tab (#58)
* This fixes an issue where errors when editing documents in an API for MongoDB endpoint would not be presented in the UI.

* Changing null to undefined in several places

* Fixed style issue.
Unignored MongoProxyClient.ts from full lint

* More linter issues since the removal from lint ignore
2020-06-29 16:02:31 -07:00
Laurent Nguyen
8200cc521f Switch to Graph explorer gremlin queries to use id and pk inside single quoted strings (#57) 2020-06-26 16:52:54 +02:00
Laurent Nguyen
1d3b672a14 Fix focus to match portal (#56) 2020-06-26 16:52:28 +02:00
Steve Faulkner
e5fc6f2022 Runner Tweaks (#62)
Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
2020-06-25 18:59:44 -05:00
Steve Faulkner
3bf42b23dd Initial Portal Runner (#51) 2020-06-24 14:07:01 -05:00
Steve Faulkner
d22cb598a9 Fix Typo (#54) 2020-06-24 13:35:30 -05:00
Steve Faulkner
269ea6a349 Add Additional Lint Rules (#55) 2020-06-23 10:45:51 -05:00
Laurent Nguyen
123902e7ee Allow multi-line input for query box in Graph (#41) 2020-06-23 09:35:16 +02:00
Steve Faulkner
bccebaade5 Update Webpack Plugins (#50) 2020-06-18 08:39:47 -05:00
Laurent Nguyen
9fedf63a77 Show splash screen for all accounts (#44) 2020-06-18 10:36:05 +02:00
Laurent Nguyen
4abfcc5e25 Fix graph tab height issue (bottom part too low and occluded by notification bar) (#48) 2020-06-17 12:17:22 -05:00
Laurent Nguyen
3d9256abc6 Add contribution guidelines (#40) 2020-06-17 08:48:39 +02:00
Steve Faulkner
7f1355b1a4 Linting Updates (#47) 2020-06-16 09:21:44 -05:00
Tanuj Mittal
3eff440680 Remove unused Telemetry Actions and update comment (#18) 2020-06-15 10:35:02 -07:00
Steve Faulkner
4da0887e5e End to End CI Test for Mongo and SQL (#33)
Co-authored-by: Ashwin Kumar M <v-asmuth@microsoft.com>
Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
2020-06-15 11:25:59 -05:00
Steve Faulkner
b783445130 Add Compile + Lint + Format to CI (#45) 2020-06-15 10:08:54 -05:00
Laurent Nguyen
73f2c612ed Remove old resource tree and cleanup (#28)
* Remove old resource tree and its various components

* Fix stored procedure, udf, trigger not always deleting when context menu option chosen

* Reformat and fix eslint warnings

* Remove CommandButtonOptions
2020-06-15 12:16:52 +02:00
Laurent Nguyen
d70e30c4fc Add data explorer launcher (#23)
* Initial migration from ADO

* Bug fixes

* Fix bugs. Make active area smaller and require shift + ctrl + dbl click

* Add missing features

* Switch from HashMap to Map as it is already polyfilled
2020-06-15 10:50:55 +02:00
Steve Faulkner
f8f1df4183 Update @azure/cosmos to 3.7.1 (#43) 2020-06-12 17:57:03 -05:00
Steve Faulkner
d427fc729e Update webpack-dev-server to latest (#32) 2020-06-12 17:34:52 -05:00
Steve Faulkner
9c36782661 Copy Contracts into dist/ (#39) 2020-06-12 12:33:00 -05:00
Laurent Nguyen
d32bd8851e Remove NotebookTab (#35) 2020-06-12 09:26:39 +02:00
Laurent Nguyen
23b2d8100f Remove obsolete feature flags (and reformat) (#27)
* Reformat

* Remove unused feature flags: graph, cacheOptimizations, settingsPane, throughputOverview, enableNteract
2020-06-11 12:39:58 +02:00
Steve Faulkner
1662d20e8a Fix Auth Header + Firefox Bug in Emulator (#22) 2020-06-10 16:08:05 -05:00
victor-meng
582ac865ff Migrate UploadItemPane to react (#17)
* Create GenericPaneComponent and use it to migrate UploadItemsPane to React

* Add helper functions for building each panel section

* Address comments and some styling changes

* Unsubscribe to isNotificationConsoleExpanded when component unmounts
2020-06-10 00:15:32 -07:00
Tanuj Mittal
aa8236666e Use graphql in GitHubClient and misc fixes (#8)
* Use graphql in GitHubClient

* Replace usage of Array.find with _.find
2020-06-05 12:22:41 -07:00
Laurent Nguyen
e9d3160b57 Initial transfer from ADO (#13) 2020-06-04 19:04:15 -07:00
Tanuj Mittal
ab3486bd05 Add telemetry to track execute cell from <Prompt/> (#15) 2020-06-02 10:46:12 -07:00
506 changed files with 59228 additions and 52040 deletions

6
.env.example Normal file
View File

@@ -0,0 +1,6 @@
# 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,4 +1,5 @@
**/node_modules/
dist/
src/Api/Apis.ts
src/AuthType.ts
src/Bindings/BindingHandlersRegisterer.ts
@@ -25,7 +26,6 @@ src/Common/Logger.test.ts
src/Common/MessageHandler.test.ts
src/Common/MessageHandler.ts
src/Common/MongoProxyClient.test.ts
src/Common/MongoProxyClient.ts
src/Common/MongoUtility.ts
src/Common/NotificationsClientBase.ts
src/Common/ObjectCache.test.ts
@@ -202,7 +202,6 @@ src/Explorer/Tabs/GraphTab.ts
src/Explorer/Tabs/MongoDocumentsTab.ts
src/Explorer/Tabs/MongoQueryTab.ts
src/Explorer/Tabs/MongoShellTab.ts
src/Explorer/Tabs/NotebookTab.ts
src/Explorer/Tabs/NotebookV2Tab.ts
src/Explorer/Tabs/QueryTab.test.ts
src/Explorer/Tabs/QueryTab.ts
@@ -216,7 +215,6 @@ src/Explorer/Tabs/TabComponents.ts
src/Explorer/Tabs/TabsBase.ts
src/Explorer/Tabs/TriggerTab.ts
src/Explorer/Tabs/UserDefinedFunctionTab.ts
src/Explorer/Tabs/__mocks__/NotebookTab.ts
src/Explorer/Tree/AccessibleVerticalList.ts
src/Explorer/Tree/Collection.test.ts
src/Explorer/Tree/Collection.ts
@@ -343,7 +341,7 @@ src/Explorer/Controls/LibraryManagement/LibraryManageComponentAdapter.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.tsx
src/Explorer/Controls/NotebookViewer/NotebookViewer.tsx
src/NotebookViewer/NotebookViewer.tsx
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
src/Explorer/Controls/QueriesGridReactComponent/QueriesGridComponent.tsx
src/Explorer/Controls/QueriesGridReactComponent/QueriesGridComponentAdapter.tsx

View File

@@ -1,45 +1,44 @@
module.exports = {
env: {
browser: true,
es6: true
es6: true,
},
plugins: ["@typescript-eslint"],
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
plugins: ["@typescript-eslint", "no-null"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
globals: {
Atomics: "readonly",
SharedArrayBuffer: "readonly"
SharedArrayBuffer: "readonly",
},
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaFeatures: {
jsx: true
jsx: true,
},
ecmaVersion: 2018,
sourceType: "module"
sourceType: "module",
},
overrides: [
{
files: ["**/*.tsx"],
env: {
jest: true
jest: true,
},
extends: ["plugin:react/recommended"],
plugins: ["react"]
plugins: ["react"],
},
{
files: ["**/*.test.{ts,tsx}"],
env: {
jest: true
jest: true,
},
extends: ["plugin:jest/recommended"],
plugins: ["jest"]
}
plugins: ["jest"],
},
],
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,6 +1,3 @@
# 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
on:
push:
@@ -8,7 +5,41 @@ on:
pull_request:
branches: [master]
jobs:
test:
compile:
runs-on: ubuntu-latest
name: "Compile TypeScript"
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- run: npm ci
- run: npm run compile
- run: npm run compile:strict
format:
runs-on: ubuntu-latest
name: "Check Format"
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- run: npm ci
- run: npm run format:check
lint:
runs-on: ubuntu-latest
name: "Lint"
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- run: npm ci
- run: npm run lint
unittest:
runs-on: ubuntu-latest
name: "Unit Tests"
steps:
@@ -36,13 +67,14 @@ jobs:
path: .cache
key: ${{ runner.os }}-build-cache
- run: npm run pack:prod
- run: npm run copyToConsumers
- run: cp -r ./Contracts ./dist/contracts
- run: cp -r ./configs ./dist/configs
- uses: actions/upload-artifact@v2
with:
name: dist
path: dist/
endtoend:
name: "End to End Tests"
endtoendemulator:
name: "End To End Tests | Emulator | SQL"
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
@@ -67,8 +99,36 @@ jobs:
EMULATOR_ENDPOINT: https://0.0.0.0:8081/
NODE_TLS_REJECT_UNAUTHORIZED: 0
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
endtoendprodcassandra:
name: "End to End Tests Prod"
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
@@ -85,17 +145,19 @@ jobs:
run: |
npm ci
npm start &
npm ci --prefix ./cypress
npm run test --prefix ./cypress
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:
EMULATOR_ENDPOINT: https://0.0.0.0:8081/
NODE_TLS_REJECT_UNAUTHORIZED: 0
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
CONNECTION_STRING: ${{ secrets.CASSANDRA_CONNECTION_STRING }}
CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
nuget:
name: Publish Nuget
needs: [build, test, endtoend]
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo]
runs-on: ubuntu-latest
env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
@@ -108,6 +170,7 @@ jobs:
uses: actions/download-artifact@v2
with:
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 pack -Version "2.0.0-github-${GITHUB_SHA}"
- run: nuget push -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
@@ -116,7 +179,7 @@ jobs:
path: "*.nupkg"
nugetmpac:
name: Publish Nuget MPAC
needs: [build, test, endtoend]
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo]
runs-on: ubuntu-latest
env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
@@ -129,6 +192,7 @@ jobs:
uses: actions/download-artifact@v2
with:
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: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "GitHub" -Password "$AZURE_DEVOPS_PAT"
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"

20
.github/workflows/runners.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
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,3 +16,4 @@ notebookapp/*
Contracts/*
.DS_Store
.cache/
.env

51
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,51 @@
# Contribution guidelines to Data Explorer
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.microsoft.com.
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
## Microsoft Open Source Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
Resources:
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
## Browser support
Please make sure to support all modern browsers as well as Internet Explorer 11.
For IE support, polyfill is preferred over new usage of lodash or underscore. We already polyfill almost everything by importing babel-polyfill at the top of entry points.
## Coding guidelines, conventions and recommendations
### Typescript
* Follow this [typescript style guide](https://github.com/excelmicro/typescript) which is based on [airbnb's style guide](https://github.com/airbnb/javascript).
* Conventions speficic to this project:
* Use double-quotes for string
* Don't use null, use undefined
* Pascal case for private static readonly fields
* Camel case for classnames in markup
* Don't use class unless necessary
* Code related to notebooks should be dynamically imported so that it is loaded from a separate bundle only if the account is notebook-enabled. There are already top-level notebook components which are dynamically imported and their dependencies can be statically imported from these files.
### React
* Prefer using React class components over function components and hooks unless you have a simple component and require no nested functions:
* Nested functions may be harder to test independently
* Switching from function component to class component later mayb be painful
## Testing
Any PR should not decrease testing coverage.
## Recommended Tools and VS Code extensions
* [Bookmarks](https://github.com/alefragnani/vscode-bookmarks)
* [Bracket pair colorizer](https://github.com/CoenraadS/Bracket-Pair-Colorizer-2)
* [GitHub Pull Requests and Issues](https://github.com/Microsoft/vscode-pull-request-github)

View File

@@ -70,7 +70,7 @@ Unit tests are located adjacent to the code under test and run with [Jest](https
`npm run test`
#### End to End Tests
#### End to End CI 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,16 +80,13 @@ Unit tests are located adjacent to the code under test and run with [Jest](https
4. Install dependencies: `npm install`
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
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.microsoft.com.
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
Please read the [contribution guidelines](./CONTRIBUTING.md).

View File

@@ -1,3 +1,3 @@
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"],
};

3
configs/mpac.json Normal file
View File

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

3
configs/prod.json Normal file
View File

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

3
cypress/.gitignore vendored
View File

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

51
cypress/cleanup.js Normal file
View File

@@ -0,0 +1,51 @@
// Cleans up old databases from previous test runs
const { CosmosClient } = require("@azure/cosmos");
// TODO: Add support for other API connection strings
const mongoRegex = RegExp("mongodb://.*:(.*)@(.*).mongo.cosmos.azure.com");
async function cleanup() {
const connectionString = process.env.CYPRESS_CONNECTION_STRING;
if (!connectionString) {
throw new Error("Connection string not provided");
}
let client;
switch (true) {
case connectionString.includes("mongodb://"): {
const [, key, accountName] = connectionString.match(mongoRegex);
client = new CosmosClient({
key,
endpoint: `https://${accountName}.documents.azure.com:443/`
});
break;
}
// TODO: Add support for other API connection strings
default:
client = new CosmosClient(connectionString);
break;
}
const response = await client.databases.readAll().fetchAll();
return Promise.all(
response.resources.map(async db => {
const dbTimestamp = new Date(db._ts * 1000);
const twentyMinutesAgo = new Date(Date.now() - 1000 * 60 * 20);
if (dbTimestamp < twentyMinutesAgo) {
await client.database(db.id).delete();
console.log(`DELETED: ${db.id} | Timestamp: ${dbTimestamp}`);
} else {
console.log(`SKIPPED: ${db.id} | Timestamp: ${dbTimestamp}`);
}
})
);
}
cleanup()
.then(() => {
process.exit(0);
})
.catch(error => {
console.error(error);
process.exit(1);
});

View File

@@ -23,7 +23,7 @@ context("Cassandra API Test - createDatabase", () => {
const keyspaceId = `KeyspaceId${crypt.randomBytes(8).toString("hex")}`;
const tableId = `TableId112`;
cy.get("iframe").then($element => {
cy.get("iframe").then(($element) => {
const $body = $element.contents().find("body");
cy.wrap($body)
.find('div[class="commandBarContainer"]')
@@ -32,27 +32,15 @@ context("Cassandra API Test - createDatabase", () => {
.should("be.visible")
.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);

View File

@@ -24,7 +24,7 @@ context("Graph API Test", () => {
const graphId = `TestGraph${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");
cy.wrap($body)
.find('div[class="commandBarContainer"]')
@@ -33,39 +33,21 @@ context("Graph API Test", () => {
.should("be.visible")
.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);

View File

@@ -16,7 +16,7 @@ let crypt = require("crypto");
context("Mongo API Test - createDatabase", () => {
beforeEach(() => {
connectionString.loginUsingConnectionString(connectionString.constants.mongo);
connectionString.loginUsingConnectionString();
});
it("Create a new collection in Mongo API", () => {
@@ -24,7 +24,7 @@ context("Mongo API Test - createDatabase", () => {
const collectionId = `TestCollection${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");
cy.wrap($body)
.find('div[class="commandBarContainer"]')
@@ -33,38 +33,21 @@ context("Mongo API Test - createDatabase", () => {
.should("be.visible")
.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('input[data-test="addCollection-createCollection"]')
.click();
cy.wrap($body).find("#submitBtnAddCollection").click();
cy.wait(10000);

View File

@@ -16,15 +16,15 @@ let crypt = require("crypto");
context("Mongo API Test", () => {
beforeEach(() => {
connectionString.loginUsingConnectionString(connectionString.constants.mongo);
connectionString.loginUsingConnectionString();
});
it("Create a new collection in Mongo API - Autopilot", () => {
it.skip("Create a new collection in Mongo API - Autopilot", () => {
const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`;
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then($element => {
cy.get("iframe").then(($element) => {
const $body = $element.contents().find("body");
cy.wrap($body)
.find('div[class="commandBarContainer"]')
@@ -33,34 +33,23 @@ context("Mongo API Test", () => {
.should("be.visible")
.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('div[class="throughputModeContainer"]')
.should("be.visible")
.and(input => {
.and((input) => {
expect(input.get(0).textContent, "first item").contains("Autopilot (preview)");
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)
.find('select[name="autoPilotTiers"]')
@@ -68,19 +57,13 @@ context("Mongo API Test", () => {
// // .select('4,000 RU/s').should('have.value', '1');
.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);

View File

@@ -4,20 +4,20 @@ let crypt = require("crypto");
context("Mongo API Test", () => {
beforeEach(() => {
connectionString.loginUsingConnectionString(connectionString.constants.mongo);
connectionString.loginUsingConnectionString();
});
it("Create a new collection in existing database in Mongo API", () => {
it.skip("Create a new collection in existing database in Mongo API", () => {
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then($element => {
cy.get("iframe").then(($element) => {
const $body = $element.contents().find("body");
cy.wrap($body)
.find('span[class="nodeLabel"]')
.should("be.visible")
.then($span => {
.then(($span) => {
const dbId1 = $span.text();
cy.log("DBBB", dbId1);
@@ -28,30 +28,17 @@ context("Mongo API Test", () => {
.should("be.visible")
.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);

View File

@@ -2,9 +2,9 @@ const connectionString = require("../../../utilities/connectionString");
let crypt = require("crypto");
context("Mongo API Test", () => {
context.skip("Mongo API Test", () => {
beforeEach(() => {
connectionString.loginUsingConnectionString(connectionString.constants.mongo);
connectionString.loginUsingConnectionString();
});
it("Create a new collection in Mongo API - Provision database throughput", () => {
@@ -12,7 +12,7 @@ context("Mongo API Test", () => {
const collectionId = `TestCollection${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");
cy.wrap($body)
.find('div[class="commandBarContainer"]')
@@ -21,50 +21,31 @@ context("Mongo API Test", () => {
.should("be.visible")
.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(".createNewDatabaseOrUseExisting")
.should("have.length", 2)
.and(input => {
.and((input) => {
expect(input.get(0).textContent, "first item").contains("Create new");
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);
@@ -84,7 +65,7 @@ context("Mongo API Test", () => {
const collectionIdTitle = `Add Collection`;
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then($element => {
cy.get("iframe").then(($element) => {
const $body = $element.contents().find("body");
cy.wrap($body)
.find('div[class="commandBarContainer"]')
@@ -93,42 +74,23 @@ context("Mongo API Test", () => {
.should("be.visible")
.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);
@@ -147,7 +109,7 @@ context("Mongo API Test", () => {
const collectionId = `TestCollection${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");
cy.wrap($body)
.find('div[class="commandBarContainer"]')
@@ -156,38 +118,21 @@ context("Mongo API Test", () => {
.should("be.visible")
.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);

View File

@@ -16,15 +16,16 @@ let crypt = require("crypto");
context("SQL API Test", () => {
beforeEach(() => {
connectionString.loginUsingConnectionString(connectionString.constants.sql);
connectionString.loginUsingConnectionString();
});
it("Create a new container in SQL API", () => {
const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`;
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`;
connectionString.loginUsingConnectionString();
cy.get("iframe").then($element => {
cy.get("iframe").then(($element) => {
const $body = $element.contents().find("body");
cy.wrap($body)
.find('div[class="commandBarContainer"]')
@@ -33,38 +34,21 @@ context("SQL API Test", () => {
.should("be.visible")
.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('input[data-test="addCollection-createCollection"]')
.click();
cy.wrap($body).find("#submitBtnAddCollection").click();
cy.wait(10000);

View File

@@ -22,7 +22,7 @@ context("Table API Test", () => {
it("Create a new table in Table API", () => {
const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`;
cy.get("iframe").then($element => {
cy.get("iframe").then(($element) => {
const $body = $element.contents().find("body");
cy.wrap($body)
.find('div[class="commandBarContainer"]')
@@ -31,22 +31,13 @@ context("Table API Test", () => {
.should("be.visible")
.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);

View File

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

View File

@@ -38,27 +38,15 @@ context("Emulator - Create database -> container -> item", () => {
cy.get("[data-test=addCollection-partitionKeyValue]").type("/pk");
cy.get('input[name="createCollection"]').click();
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")
.contains(collectionId)
.click();
cy.get(".dataResourceTree")
.contains("Items")
.click();
cy.get(".dataResourceTree")
.contains("Items")
.click();
cy.get(".dataResourceTree").contains(collectionId).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.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.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.get(".documentsGridHeaderContainer").should("contain", "replace_with_new_document_id");
});

View File

@@ -14,25 +14,18 @@ context("Emulator - deleteCollection", () => {
});
it("Delete a collection", () => {
cy.get(".databaseId")
.last()
.click();
cy.get(".databaseId").last().click();
cy.get(".collectionList")
.last()
.then($id => {
.then(($id) => {
const collectionId = $id.text();
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());

View File

@@ -22,10 +22,10 @@ context("Emulator - deleteDatabase", () => {
url: "https://localhost:8081/_explorer/authorization/post/dbs/",
headers: {
"x-ms-date": date,
authorization: "-"
}
authorization: "-",
},
})
.then(response => {
.then((response) => {
authToken = response.body.Token; // Getting auth token for collection creation
return new Cypress.Promise((resolve, reject) => {
return resolve();
@@ -38,12 +38,12 @@ context("Emulator - deleteDatabase", () => {
headers: {
"x-ms-date": date,
authorization: authToken,
"x-ms-version": "2018-12-31"
"x-ms-version": "2018-12-31",
},
body: {
id: dbId
}
}).then(response => {
id: dbId,
},
}).then((response) => {
cy.log("Response", response);
db_rid = response.body._rid;
return new Cypress.Promise((resolve, reject) => {
@@ -59,19 +59,14 @@ context("Emulator - deleteDatabase", () => {
cy.get(".databaseId")
.last()
.then($id => {
.then(($id) => {
const dbId = $id.text();
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());

View File

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

View File

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

View File

@@ -273,77 +273,12 @@
"any-observable": "^0.3.0"
}
},
"@types/blob-util": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@types/blob-util/-/blob-util-1.3.3.tgz",
"integrity": "sha512-4ahcL/QDnpjWA2Qs16ZMQif7HjGP2cw3AGjHabybjw7Vm1EKu+cfQN1D78BaZbS1WJNa1opSMF5HNMztx7lR0w==",
"@types/sinonjs__fake-timers": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz",
"integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==",
"dev": true
},
"@types/bluebird": {
"version": "3.5.29",
"resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.29.tgz",
"integrity": "sha512-kmVtnxTuUuhCET669irqQmPAez4KFnFVKvpleVRyfC3g+SHD1hIkFZcWLim9BVcwUBLO59o8VZE4yGCmTif8Yw==",
"dev": true
},
"@types/chai": {
"version": "4.2.7",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.7.tgz",
"integrity": "sha512-luq8meHGYwvky0O7u0eQZdA7B4Wd9owUCqvbw2m3XCrCU8mplYOujMBbvyS547AxJkC+pGnd0Cm15eNxEUNU8g==",
"dev": true
},
"@types/chai-jquery": {
"version": "1.1.40",
"resolved": "https://registry.npmjs.org/@types/chai-jquery/-/chai-jquery-1.1.40.tgz",
"integrity": "sha512-mCNEZ3GKP7T7kftKeIs7QmfZZQM7hslGSpYzKbOlR2a2HCFf9ph4nlMRA9UnuOETeOQYJVhJQK7MwGqNZVyUtQ==",
"dev": true,
"requires": {
"@types/chai": "*",
"@types/jquery": "*"
}
},
"@types/jquery": {
"version": "3.3.31",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.31.tgz",
"integrity": "sha512-Lz4BAJihoFw5nRzKvg4nawXPzutkv7wmfQ5121avptaSIXlDNJCUuxZxX/G+9EVidZGuO0UBlk+YjKbwRKJigg==",
"dev": true,
"requires": {
"@types/sizzle": "*"
}
},
"@types/lodash": {
"version": "4.14.149",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz",
"integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==",
"dev": true
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
"dev": true
},
"@types/mocha": {
"version": "5.2.7",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz",
"integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==",
"dev": true
},
"@types/sinon": {
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.1.tgz",
"integrity": "sha512-EZQUP3hSZQyTQRfiLqelC9NMWd1kqLcmQE0dMiklxBkgi84T+cHOhnKpgk4NnOWpGX863yE6+IaGnOXUNFqDnQ==",
"dev": true
},
"@types/sinon-chai": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.3.tgz",
"integrity": "sha512-TOUFS6vqS0PVL1I8NGVSNcFaNJtFoyZPXZ5zur+qlhDfOmQECZZM4H4kKgca6O8L+QceX/ymODZASfUfn+y4yQ==",
"dev": true,
"requires": {
"@types/chai": "*",
"@types/sinon": "*"
}
},
"@types/sizzle": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz",
@@ -827,24 +762,15 @@
}
},
"cypress": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-4.5.0.tgz",
"integrity": "sha512-2A4g5FW5d2fHzq8HKUGAMVTnW6P8nlWYQALiCoGN4bqBLvgwhYM/oG9oKc2CS6LnvgHFiKivKzpm9sfk3uU3zQ==",
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-4.8.0.tgz",
"integrity": "sha512-Lsff8lF8pq6k/ioNua783tCsxGSLp6gqGXecdIfqCkqjYiOA53XKuEf1CaQJLUVs1dHSf49eDUp/sb620oJjVQ==",
"dev": true,
"requires": {
"@cypress/listr-verbose-renderer": "0.4.1",
"@cypress/request": "2.88.5",
"@cypress/xvfb": "1.2.4",
"@types/blob-util": "1.3.3",
"@types/bluebird": "3.5.29",
"@types/chai": "4.2.7",
"@types/chai-jquery": "1.1.40",
"@types/jquery": "3.3.31",
"@types/lodash": "4.14.149",
"@types/minimatch": "3.0.3",
"@types/mocha": "5.2.7",
"@types/sinon": "7.5.1",
"@types/sinon-chai": "3.2.3",
"@types/sinonjs__fake-timers": "6.0.1",
"@types/sizzle": "2.3.2",
"arch": "2.1.1",
"bluebird": "3.7.2",
@@ -878,14 +804,6 @@
"untildify": "4.0.0",
"url": "0.11.0",
"yauzl": "2.10.0"
},
"dependencies": {
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
}
}
},
"dashdash": {
@@ -1081,12 +999,6 @@
"ms": "2.0.0"
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
@@ -1859,6 +1771,12 @@
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",

View File

@@ -5,11 +5,13 @@
"main": "index.js",
"scripts": {
"test": "cypress run",
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",
"test:sql": "cypress run --browser chrome --headless --spec \"./integration/dataexplorer/SQL/*\"",
"test:ci": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/ https-get://0.0.0.0:8081/_explorer/index.html && cypress run --browser chrome --headless",
"test:debug": "cypress open"
},
"devDependencies": {
"cypress": "^4.5.0",
"cypress": "^4.8.0",
"mocha": "^7.0.1",
"mochawesome": "^4.1.0",
"mochawesome-merge": "^4.0.1",

View File

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

9
jest-puppeteer.config.js Normal file
View File

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

5
jest.config.e2e.js Normal file
View File

@@ -0,0 +1,5 @@
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,
functions: 22,
lines: 28,
statements: 27
}
statements: 27,
},
},
// 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
"^dnd-core$": "dnd-core/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
@@ -150,7 +150,7 @@ module.exports = {
// testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?|ts?)$",
// 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
// testRunner: "jasmine2",
@@ -164,11 +164,11 @@ module.exports = {
// A map from regular expressions to paths to transformers
transform: {
"^.+\\.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
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
// unmockedModulePathPatterns: undefined,

View File

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

View File

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

10146
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,24 +0,0 @@
@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",
EncryptedToken = "encryptedtoken",
MasterKey = "masterkey",
ResourceToken = "resourcetoken"
ResourceToken = "resourcetoken",
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,13 +21,15 @@ const _global = typeof self === "undefined" ? window : self;
export const tokenProvider = async (requestInfo: RequestInfo) => {
const { verb, resourceId, resourceType, headers } = requestInfo;
if (config.platform === Platform.Emulator) {
// TODO Remove any. SDK expects a return value for tokenProvider, but we are mutating the header object instead.
return setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey) as any;
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
return decodeURIComponent(headers.authorization);
}
if (_masterKey) {
// TODO Remove any. SDK expects a return value for tokenProvider, but we are mutating the header object instead.
return setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, _masterKey) as any;
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
return decodeURIComponent(headers.authorization);
}
if (_resourceToken) {
@@ -47,7 +49,9 @@ export const requestPlugin: Cosmos.Plugin<any> = async (requestContext, next) =>
export const endpoint = () => {
if (config.platform === Platform.Emulator) {
return config.EMULATOR_ENDPOINT || window.parent.location.origin;
// In worker scope, _global(self).parent does not exist
const location = _global.parent ? _global.parent.location : _global.location;
return config.EMULATOR_ENDPOINT || location.origin;
}
return _endpoint || (_databaseAccount && _databaseAccount.properties && _databaseAccount.properties.documentEndpoint);
};
@@ -59,13 +63,13 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
method: "POST",
headers: {
"content-type": "application/json",
"x-ms-encrypted-auth-token": _accessToken
"x-ms-encrypted-auth-token": _accessToken,
},
body: JSON.stringify({
verb,
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()
const result = JSON.parse(await response.json());
@@ -89,9 +93,9 @@ export const CosmosClient = {
key: _masterKey,
tokenProvider,
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.
@@ -172,5 +176,5 @@ export const CosmosClient = {
_client = null;
_resourceToken = value;
return value;
}
},
};

View File

@@ -14,7 +14,7 @@ import {
PartitionKeyDefinition,
QueryIterator,
Resource,
TriggerDefinition
TriggerDefinition,
} from "@azure/cosmos";
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
import { CosmosClient } from "./CosmosClient";
@@ -68,7 +68,7 @@ export abstract class DataAccessUtilityBase {
.container(collection.id())
.scripts.storedProcedures.readAll(options)
.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())
.scripts.storedProcedure(requestedResource.id)
.read(options)
.then(response => response.resource as DataModels.StoredProcedure)
.then((response) => response.resource as DataModels.StoredProcedure)
);
}
public readUserDefinedFunctions(
@@ -96,7 +96,7 @@ export abstract class DataAccessUtilityBase {
.container(collection.id())
.scripts.userDefinedFunctions.readAll(options)
.fetchAll()
.then(response => response.resources as DataModels.UserDefinedFunction[])
.then((response) => response.resources as DataModels.UserDefinedFunction[])
);
}
public readUserDefinedFunction(
@@ -110,7 +110,7 @@ export abstract class DataAccessUtilityBase {
.container(collection.id())
.scripts.userDefinedFunction(requestedResource.id)
.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())
.scripts.triggers.readAll(options)
.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())
.scripts.trigger(requestedResource.id)
.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())
.scripts.storedProcedure(storedProcedure.id())
.execute(partitionKeyValue, params, { enableScriptLogging: true })
.then(response =>
.then((response) =>
deferred.resolve({
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(
Constants.ClientDefaults.requestTimeoutMs,
@@ -177,7 +177,7 @@ export abstract class DataAccessUtilityBase {
.container(collection.id())
.item(documentId.id(), partitionKey)
.read()
.then(response => response.resource)
.then((response) => response.resource)
);
}
@@ -230,7 +230,7 @@ export abstract class DataAccessUtilityBase {
.container(collection.id())
.item(documentId.id(), partitionKey)
.replace(newDocument)
.then(response => response.resource)
.then((response) => response.resource)
);
}
@@ -243,7 +243,7 @@ export abstract class DataAccessUtilityBase {
CosmosClient.client()
.offer(offer.id)
.replace(newOffer, options)
.then(response => {
.then((response) => {
return Promise.all([this.refreshCachedOffers(), this.refreshCachedResources()]).then(() => response.resource);
})
);
@@ -260,7 +260,7 @@ export abstract class DataAccessUtilityBase {
.container(collection.id())
.scripts.storedProcedure(storedProcedure.id)
.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())
.scripts.userDefinedFunction(userDefinedFunction.id)
.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())
.scripts.trigger(trigger.id)
.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)
.container(collection.id())
.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)
.container(collection.id())
.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)
.container(collection.id())
.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)
.container(collection.id())
.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())
.containers.readAll()
.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)
.container(collectionId)
.read()
.then(response => response.resource)
.then((response) => response.resource)
);
}
@@ -491,7 +491,7 @@ export abstract class DataAccessUtilityBase {
CosmosClient.client()
.offers.readAll()
.fetchAll()
.then(response => response.resources)
.then((response) => response.resources)
);
}
@@ -506,7 +506,7 @@ export abstract class DataAccessUtilityBase {
CosmosClient.client()
.offer(requestedResource.id)
.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()
.databases.readAll()
.fetchAll()
.then(response => response.resources as DataModels.Database[])
.then((response) => response.resources as DataModels.Database[])
);
}
@@ -533,11 +533,11 @@ export abstract class DataAccessUtilityBase {
uniqueKeyPolicy,
offerThroughput,
analyticalStorageTtl,
hasAutoPilotV2FeatureFlag
hasAutoPilotV2FeatureFlag,
} = request;
const createBody: DatabaseRequest = {
id: databaseId
id: databaseId,
};
// TODO: replace when SDK support autopilot
@@ -545,11 +545,11 @@ export abstract class DataAccessUtilityBase {
? !hasAutoPilotV2FeatureFlag
? {
[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;
if (databaseLevelThroughput) {
@@ -562,7 +562,7 @@ export abstract class DataAccessUtilityBase {
return Q(
CosmosClient.client()
.databases.createIfNotExists(createBody, databaseOptions)
.then(response => {
.then((response) => {
return response.database.containers.create(
{
id: collectionId,
@@ -570,14 +570,14 @@ export abstract class DataAccessUtilityBase {
indexingPolicy: indexingPolicy ? indexingPolicy : undefined,
uniqueKeyPolicy: uniqueKeyPolicy ? uniqueKeyPolicy : undefined,
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
{
initialHeaders: databaseLevelThroughput ? undefined : initialHeaders
initialHeaders: databaseLevelThroughput ? undefined : initialHeaders,
}
);
})
.then(containerResponse => containerResponse.resource)
.then((containerResponse) => containerResponse.resource)
.finally(() => this.refreshCachedResources(options))
);
}
@@ -591,7 +591,7 @@ export abstract class DataAccessUtilityBase {
deferred.resolve(createdDatabase);
});
},
_createDatabaseError => {
(_createDatabaseError) => {
deferred.reject(_createDatabaseError);
}
);
@@ -654,10 +654,10 @@ export abstract class DataAccessUtilityBase {
const initialHeaders = autoPilot
? !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;
if (!!databaseLevelThroughput) {

View File

@@ -6,7 +6,7 @@ import Q from "q";
import { ConflictDefinition, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { DataAccessUtilityBase } from "./DataAccessUtilityBase";
import { Logger } from "./Logger";
import * as Logger from "./Logger";
import { MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { MinimalQueryIterator, nextPage } from "./IteratorUtilities";
@@ -376,7 +376,7 @@ export default class DocumentClientUtilityBase {
JSON.stringify({
oldOffer: offer,
newOffer: newOffer,
error: error
error: error,
}),
"UpdateOffer",
error.code
@@ -583,7 +583,7 @@ export default class DocumentClientUtilityBase {
);
deferred.resolve(createdStoredProcedure);
},
error => {
(error) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.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);
},
error => {
(error) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.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({
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;
});
observable.subscribe(edit => {
observable.subscribe((edit) => {
var edits = observable.edits && observable.edits();
if (!edits) {
return;
@@ -83,9 +83,9 @@ export default class EditableUtility {
});
observable.editableIsValid = ko.observable<boolean>(true);
observable.subscribe(value => {
observable.subscribe((value) => {
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);
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,84 +4,68 @@ import { appInsights } from "../Shared/appInsights";
import { SeverityLevel } from "@microsoft/applicationinsights-web";
// TODO: Move to a separate Diagnostics folder
export class Logger {
public static logInfo(message: string | Record<string, any>, 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 = Logger._generateLogEntry(
Diagnostics.LogEntryLevel.Verbose,
logMessage,
area,
code
);
return Logger._logEntry(entry);
}
public static logWarning(message: string, area: string, code?: number): void {
const entry: Diagnostics.LogEntry = Logger._generateLogEntry(
Diagnostics.LogEntryLevel.Warning,
message,
area,
code
);
return Logger._logEntry(entry);
}
public static 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 = 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
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function logInfo(message: string | Record<string, any>, 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.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);
appInsights.trackTrace({ message: entry.message, severityLevel }, { area: entry.area });
}
function _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> = {
id: "123",
startTime: new Date(),
deferred: Q.defer<any>()
deferred: Q.defer<any>(),
};
let mockMessage = { message: { id: "123", data: "{}" } };
@@ -54,7 +54,7 @@ describe("Message Handler", () => {
let mockPromise: CachedDataPromise<any> = {
id: "123",
startTime: new Date(),
deferred: Q.defer<any>()
deferred: Q.defer<any>(),
};
MockMessageHandler.addToMap(mockPromise.id, mockPromise);

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,7 @@
*/
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) {
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 tabSpace = nolint ? "" : "\t";
indent = indent || "";
@@ -114,7 +114,7 @@ export default class MongoUtility {
}
}
// Add proper line endings, indents, and commas to each line
s += $.map(pairs, function(pair) {
s += $.map(pairs, function (pair) {
return lineEnding + indent + pair;
}).join(",");
s += lineEnding;
@@ -124,7 +124,7 @@ export default class MongoUtility {
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) {
return "[ ]";
}
@@ -151,7 +151,7 @@ export default class MongoUtility {
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) {
return false;
} else if (obj.hasOwnProperty(prop)) {

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ import { SplitterMetrics } from "./Constants";
export enum SplitterDirection {
Horizontal = "horizontal",
Vertical = "vertical"
Vertical = "vertical",
}
export interface SplitterBounds {
@@ -50,7 +50,7 @@ export class Splitter {
animate: true,
animateDuration: "fast",
start: this.onResizeStart,
stop: this.onResizeStop
stop: this.onResizeStop,
};
if (isVerticalSplitter) {
@@ -90,9 +90,7 @@ export class Splitter {
this.lastWidth = $(this.leftSide).width();
$(this.splitter).css("left", SplitterMetrics.CollapsedPositionLeft);
$(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.isCollapsed(true);
}

View File

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

View File

@@ -1,7 +1,7 @@
export enum Platform {
Portal = "Portal",
Hosted = "Hosted",
Emulator = "Emulator"
Emulator = "Emulator",
}
interface Config {
@@ -45,7 +45,7 @@ let config: Config = {
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
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

View File

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

View File

@@ -56,7 +56,7 @@ export enum ApiKind {
Table,
Cassandra,
Graph,
MongoDBCompute
MongoDBCompute,
}
export interface GenerateTokenResponse {
@@ -297,7 +297,7 @@ export enum AutopilotTier {
Tier1 = 1,
Tier2 = 2,
Tier3 = 3,
Tier4 = 4
Tier4 = 4,
}
export interface RpOptions {
@@ -363,7 +363,7 @@ export interface Notification {
export enum ConflictResolutionMode {
Custom = "Custom",
LastWriterWins = "LastWriterWins"
LastWriterWins = "LastWriterWins",
}
/**
@@ -522,7 +522,7 @@ export interface SparkClusterEndpoint {
export enum SparkClusterEndpointKind {
SparkUI = "SparkUI",
HistoryServerUI = "HistoryServerUI",
Livy = "Livy"
Livy = "Livy",
}
export interface RpParameters {
@@ -704,47 +704,6 @@ export interface MemoryUsageInfo {
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;
}
export interface LikedNotebooksJunoResponse {
likedNotebooksContent: GitHubInfoJunoResponse[];
userMetadata: UserMetadata;
}
export interface resourceTokenConnectionStringProperties {
accountEndpoint: string;
collectionId: string;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,10 +4,6 @@ import * as ko from "knockout";
import "./ComponentRegisterer";
describe("Component Registerer", () => {
it("should register command-button component", () => {
expect(ko.components.isRegistered("command-button")).toBe(true);
});
it("should register input-typeahead component", () => {
expect(ko.components.isRegistered("input-typeahead")).toBe(true);
});
@@ -64,10 +60,6 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("graph-tab")).toBe(true);
});
it("should register notebook-tab component", () => {
expect(ko.components.isRegistered("notebook-tab")).toBe(true);
});
it("should register notebookv2-tab component", () => {
expect(ko.components.isRegistered("notebookv2-tab")).toBe(true);
});
@@ -84,30 +76,6 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("mongo-shell-tab")).toBe(true);
});
it("should register resource-tree component", () => {
expect(ko.components.isRegistered("resource-tree")).toBe(true);
});
it("should register database-node component", () => {
expect(ko.components.isRegistered("database-node")).toBe(true);
});
it("should register collection-node component", () => {
expect(ko.components.isRegistered("collection-node")).toBe(true);
});
it("should register stored-procedure-node component", () => {
expect(ko.components.isRegistered("stored-procedure-node")).toBe(true);
});
it("should register trigger-node component", () => {
expect(ko.components.isRegistered("trigger-node")).toBe(true);
});
it("should register user-defined-function-node component", () => {
expect(ko.components.isRegistered("user-defined-function-node")).toBe(true);
});
it("should registeradd-collection-pane component", () => {
expect(ko.components.isRegistered("add-collection-pane")).toBe(true);
});
@@ -156,10 +124,6 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("manage-spark-cluster-pane")).toBe(true);
});
it("should register collection-node-context-menu component", () => {
expect(ko.components.isRegistered("collection-node-context-menu")).toBe(true);
});
it("should register dynamic-list component", () => {
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
});

View File

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

View File

@@ -1,6 +1,5 @@
import * as ko from "knockout";
import * as ViewModels from "../Contracts/ViewModels";
import { CommandButtonOptions } from "./Controls/CommandButton/CommandButton";
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
import AddCollectionIcon from "../../images/AddCollection.svg";
import AddSqlQueryIcon from "../../images/AddSqlQuery_16x16.svg";
@@ -33,13 +32,13 @@ export class ResourceTreeContextMenuButtonFactory {
const newCollectionMenuItem: TreeNodeMenuItem = {
iconSrc: AddCollectionIcon,
onClick: () => container.onNewCollectionClicked(),
label: container.addCollectionText()
label: container.addCollectionText(),
};
const deleteDatabaseMenuItem = {
iconSrc: DeleteDatabaseIcon,
onClick: () => container.deleteDatabaseConfirmationPane.open(),
label: container.deleteDatabaseText()
label: container.deleteDatabaseText(),
};
return [newCollectionMenuItem, deleteDatabaseMenuItem];
}
@@ -53,7 +52,7 @@ export class ResourceTreeContextMenuButtonFactory {
items.push({
iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null),
label: "New SQL Query"
label: "New SQL Query",
});
}
@@ -61,7 +60,7 @@ export class ResourceTreeContextMenuButtonFactory {
items.push({
iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null),
label: "New Query"
label: "New Query",
});
items.push({
@@ -70,7 +69,7 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewMongoShellClick();
},
label: "New Shell"
label: "New Shell",
});
}
@@ -81,7 +80,7 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
},
label: "New Stored Procedure"
label: "New Stored Procedure",
});
items.push({
@@ -90,7 +89,7 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, null);
},
label: "New UDF"
label: "New UDF",
});
items.push({
@@ -99,7 +98,7 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, null);
},
label: "New Trigger"
label: "New Trigger",
});
}
@@ -109,13 +108,16 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onDeleteCollectionContextMenuClick(selectedCollection, null);
},
label: container.deleteCollectionText()
label: container.deleteCollectionText(),
});
return items;
}
public static createStoreProcedureContextMenuItems(container: ViewModels.Explorer): TreeNodeMenuItem[] {
public static createStoreProcedureContextMenuItems(
container: ViewModels.Explorer,
storedProcedure: ViewModels.StoredProcedure
): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) {
return [];
}
@@ -123,16 +125,16 @@ export class ResourceTreeContextMenuButtonFactory {
return [
{
iconSrc: DeleteSprocIcon,
onClick: () => {
const selectedStoreProcedure: ViewModels.StoredProcedure = container.findSelectedStoredProcedure();
selectedStoreProcedure && selectedStoreProcedure.delete(selectedStoreProcedure, null);
},
label: "Delete Store Procedure"
}
onClick: () => storedProcedure.delete(),
label: "Delete Store Procedure",
},
];
}
public static createTriggerContextMenuItems(container: ViewModels.Explorer): TreeNodeMenuItem[] {
public static createTriggerContextMenuItems(
container: ViewModels.Explorer,
trigger: ViewModels.Trigger
): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) {
return [];
}
@@ -140,16 +142,16 @@ export class ResourceTreeContextMenuButtonFactory {
return [
{
iconSrc: DeleteTriggerIcon,
onClick: () => {
const selectedTrigger: ViewModels.Trigger = container.findSelectedTrigger();
selectedTrigger && selectedTrigger.delete(selectedTrigger, null);
},
label: "Delete Trigger"
}
onClick: () => trigger.delete(),
label: "Delete Trigger",
},
];
}
public static createUserDefinedFunctionContextMenuItems(container: ViewModels.Explorer): TreeNodeMenuItem[] {
public static createUserDefinedFunctionContextMenuItems(
container: ViewModels.Explorer,
userDefinedFunction: ViewModels.UserDefinedFunction
): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) {
return [];
}
@@ -157,266 +159,9 @@ export class ResourceTreeContextMenuButtonFactory {
return [
{
iconSrc: DeleteUDFIcon,
onClick: () => {
const selectedUDF: ViewModels.UserDefinedFunction = container.findSelectedUDF();
selectedUDF && selectedUDF.delete(selectedUDF, null);
},
label: "Delete User Defined Function"
}
onClick: () => userDefinedFunction.delete(),
label: "Delete User Defined Function",
},
];
}
}
/**
* Current resource tree (in KO)
* TODO: Remove when switching to new resource tree
*/
export class ContextMenuButtonFactory {
public static createDatabaseContextMenuButton(
container: ViewModels.Explorer,
btnParams: DatabaseContextMenuButtonParams
): CommandButtonOptions[] {
const addCollectionId = `${btnParams.databaseId}-${container.addCollectionText()}`;
const deleteDatabaseId = `${btnParams.databaseId}-${container.deleteDatabaseText()}`;
const newCollectionButtonOptions: CommandButtonOptions = {
iconSrc: AddCollectionIcon,
id: addCollectionId,
onCommandClick: () => {
if (container.isPreferredApiCassandra()) {
container.cassandraAddCollectionPane.open();
} else {
container.addCollectionPane.open(container.selectedDatabaseId());
}
const selectedDatabase: ViewModels.Database = container.findSelectedDatabase();
selectedDatabase && selectedDatabase.contextMenu.hide(selectedDatabase, null);
},
commandButtonLabel: container.addCollectionText(),
hasPopup: true
};
const deleteDatabaseButtonOptions: CommandButtonOptions = {
iconSrc: DeleteDatabaseIcon,
id: deleteDatabaseId,
onCommandClick: () => {
const database: ViewModels.Database = container.findSelectedDatabase();
database.onDeleteDatabaseContextMenuClick(database, null);
},
commandButtonLabel: container.deleteDatabaseText(),
hasPopup: true,
disabled: ko.computed<boolean>(() => container.isNoneSelected()),
visible: ko.computed<boolean>(() => !container.isNoneSelected())
};
return [newCollectionButtonOptions, deleteDatabaseButtonOptions];
}
public static createCollectionContextMenuButton(
container: ViewModels.Explorer,
btnParams: CollectionContextMenuButtonParams
): CommandButtonOptions[] {
const newSqlQueryId = `${btnParams.databaseId}-${btnParams.collectionId}-newSqlQuery`;
const newSqlQueryForGraphId = `${btnParams.databaseId}-${btnParams.collectionId}-newSqlQueryForGraph`;
const newQueryForMongoId = `${btnParams.databaseId}-${btnParams.collectionId}-newQuery`;
const newShellForMongoId = `${btnParams.databaseId}-${btnParams.collectionId}-newShell`;
const newStoredProcedureId = `${btnParams.databaseId}-${btnParams.collectionId}-newStoredProcedure`;
const udfId = `${btnParams.databaseId}-${btnParams.collectionId}-udf`;
const newTriggerId = `${btnParams.databaseId}-${btnParams.collectionId}-newTrigger`;
const deleteCollectionId = `${btnParams.databaseId}-${btnParams.collectionId}-${container.deleteCollectionText()}`;
const newSQLQueryButtonOptions: CommandButtonOptions = {
iconSrc: AddSqlQueryIcon,
id: newSqlQueryId,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null);
},
commandButtonLabel: "New SQL Query",
hasPopup: true,
disabled: ko.computed<boolean>(
() => container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiDocumentDB()
),
visible: ko.computed<boolean>(
() => !container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiDocumentDB()
)
//TODO: Merge with add query logic below, same goes for CommandBarButtonFactory
};
const newSQLQueryButtonOptionsForGraph: CommandButtonOptions = {
iconSrc: AddSqlQueryIcon,
id: newSqlQueryForGraphId,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null);
},
commandButtonLabel: "New SQL Query",
hasPopup: true,
disabled: ko.computed<boolean>(() => container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiGraph()),
visible: ko.computed<boolean>(() => !container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiGraph())
};
const newMongoQueryButtonOptions: CommandButtonOptions = {
iconSrc: AddSqlQueryIcon,
id: newQueryForMongoId,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null);
},
commandButtonLabel: "New Query",
hasPopup: true,
disabled: ko.computed<boolean>(
() => container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()
),
visible: ko.computed<boolean>(
() => !container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()
)
};
const newMongoShellButtonOptions: CommandButtonOptions = {
iconSrc: HostedTerminalIcon,
id: newShellForMongoId,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewMongoShellClick();
},
commandButtonLabel: "New Shell",
hasPopup: true,
disabled: ko.computed<boolean>(
() => container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()
),
visible: ko.computed<boolean>(
() => !container.isDatabaseNodeOrNoneSelected() && container.isPreferredApiMongoDB()
)
};
const newStoredProcedureButtonOptions: CommandButtonOptions = {
iconSrc: AddStoredProcedureIcon,
id: newStoredProcedureId,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
},
commandButtonLabel: "New Stored Procedure",
hasPopup: true,
disabled: ko.computed<boolean>(() => container.isDatabaseNodeOrNoneSelected()),
visible: ko.computed<boolean>(
() => !container.isDatabaseNodeOrNoneSelected() && !container.isPreferredApiCassandra()
)
};
const newUserDefinedFunctionButtonOptions: CommandButtonOptions = {
iconSrc: AddUdfIcon,
id: udfId,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, null);
},
commandButtonLabel: "New UDF",
hasPopup: true,
disabled: ko.computed<boolean>(() => container.isDatabaseNodeOrNoneSelected()),
visible: ko.computed<boolean>(
() => !container.isDatabaseNodeOrNoneSelected() && !container.isPreferredApiCassandra()
)
};
const newTriggerButtonOptions: CommandButtonOptions = {
iconSrc: AddTriggerIcon,
id: newTriggerId,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, null);
},
commandButtonLabel: "New Trigger",
hasPopup: true,
disabled: ko.computed<boolean>(() => container.isDatabaseNodeOrNoneSelected()),
visible: ko.computed<boolean>(
() => !container.isDatabaseNodeOrNoneSelected() && !container.isPreferredApiCassandra()
)
};
const deleteCollectionButtonOptions: CommandButtonOptions = {
iconSrc: DeleteCollectionIcon,
id: deleteCollectionId,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onDeleteCollectionContextMenuClick(selectedCollection, null);
},
commandButtonLabel: container.deleteCollectionText(),
hasPopup: true,
disabled: ko.computed<boolean>(() => container.isDatabaseNodeOrNoneSelected()),
visible: ko.computed<boolean>(() => !container.isDatabaseNodeOrNoneSelected())
//TODO: Change to isCollectionNodeorNoneSelected and same in CommandBarButtonFactory
};
return [
newSQLQueryButtonOptions,
newSQLQueryButtonOptionsForGraph,
newMongoQueryButtonOptions,
newMongoShellButtonOptions,
newStoredProcedureButtonOptions,
newUserDefinedFunctionButtonOptions,
newTriggerButtonOptions,
deleteCollectionButtonOptions
];
}
public static createStoreProcedureContextMenuButton(container: ViewModels.Explorer): CommandButtonOptions[] {
const deleteStoredProcedureId = "Context Menu - Delete Stored Procedure";
const deleteStoreProcedureButtonOptions: CommandButtonOptions = {
iconSrc: DeleteSprocIcon,
id: deleteStoredProcedureId,
onCommandClick: () => {
const selectedStoreProcedure: ViewModels.StoredProcedure = container.findSelectedStoredProcedure();
selectedStoreProcedure && selectedStoreProcedure.delete(selectedStoreProcedure, null);
},
commandButtonLabel: "Delete Stored Procedure",
hasPopup: false,
disabled: ko.computed<boolean>(() => container.isDatabaseNodeOrNoneSelected()),
visible: ko.computed<boolean>(
() => !container.isDatabaseNodeOrNoneSelected() && !container.isPreferredApiCassandra()
)
};
return [deleteStoreProcedureButtonOptions];
}
public static createTriggerContextMenuButton(container: ViewModels.Explorer): CommandButtonOptions[] {
const deleteTriggerId = "Context Menu - Delete Trigger";
const deleteTriggerButtonOptions: CommandButtonOptions = {
iconSrc: DeleteTriggerIcon,
id: deleteTriggerId,
onCommandClick: () => {
const selectedTrigger: ViewModels.Trigger = container.findSelectedTrigger();
selectedTrigger && selectedTrigger.delete(selectedTrigger, null);
},
commandButtonLabel: "Delete Trigger",
hasPopup: false,
disabled: ko.computed<boolean>(() => container.isDatabaseNodeOrNoneSelected()),
visible: ko.computed<boolean>(
() => !container.isDatabaseNodeOrNoneSelected() && !container.isPreferredApiCassandra()
)
};
return [deleteTriggerButtonOptions];
}
public static createUserDefinedFunctionContextMenuButton(container: ViewModels.Explorer): CommandButtonOptions[] {
const deleteUserDefinedFunctionId = "Context Menu - Delete User Defined Function";
const deleteUserDefinedFunctionButtonOptions: CommandButtonOptions = {
iconSrc: DeleteUDFIcon,
id: deleteUserDefinedFunctionId,
onCommandClick: () => {
const selectedUDF: ViewModels.UserDefinedFunction = container.findSelectedUDF();
selectedUDF && selectedUDF.delete(selectedUDF, null);
},
commandButtonLabel: "Delete User Defined Function",
hasPopup: false,
disabled: ko.computed<boolean>(() => container.isDatabaseNodeOrNoneSelected()),
visible: ko.computed<boolean>(
() => !container.isDatabaseNodeOrNoneSelected() && !container.isPreferredApiCassandra()
)
};
return [deleteUserDefinedFunctionButtonOptions];
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,200 +0,0 @@
@import "../../../../less/Common/Constants";
@ButtonIconSize: 18px;
.commandBar {
padding-left: @DefaultSpace;
border-bottom: @ButtonBorderWidth solid @BaseMedium;
display: flex;
overflow: hidden;
height: @topcommandbarheight;
.staticCommands {
list-style: none;
margin: 0px;
padding: 0px;
display: flex;
flex: 0 0 auto;
}
.overflowCommands {
display:flex;
flex: 1 0 auto;
.visibleCommands {
display: inline-flex;
list-style: none;
margin: 0px;
padding: 0px;
}
.partialSplitterContainer {
padding: @SmallSpace @DefaultSpace @SmallSpace @SmallSpace;
.flex-display();
}
}
.commandExpand {
border: none;
padding: 0px;
direction: rtl;
&:hover {
.hover();
cursor: pointer;
& > .commandDropdownContainer {
display: block !important; // TODO: Remove after reusing KO mouseover and mouseout event handlers
}
}
&:focus {
.focus();
}
.commandDropdownLauncher {
direction: ltr;
padding-top: @SmallSpace;
.commandIcon {
vertical-align: text-top;
}
.commandBarEllipses {
font-weight: bold;
font-size: 20px;
}
}
}
.hiddenCommandsContainer > .commandDropdownLauncher {
padding: 0px @DefaultSpace;
}
.commandDropdownContainer {
display: none;
z-index: 1000;
direction: ltr;
position: absolute;
width: fit-content;
padding: 0px;
background-color: @BaseLight;
box-shadow: 1px 2px 6px @BaseMediumHigh, -2px 2px 6px @BaseMediumHigh;
.commandDropdown {
display: flex;
flex-direction: column;
padding: 0px;
margin: 0px;
}
}
.feedbackButton {
margin-right: @LargeSpace;
white-space: nowrap;
}
}
command-button,
.commandButtonReact {
display: inline-flex;
.commandButtonComponent {
width: 100%;
color: @BaseHigh;
background-color: transparent;
text-decoration: none;
border: @ButtonBorderWidth solid transparent;
.flex-display();
&:hover:not(.commandDisabled) {
cursor: pointer;
.hover();
}
&:active:not(.commandDisabled) {
border: @ButtonBorderWidth dashed @AccentMedium;
.active();
}
&:focus:not(.commandDisabled) {
border: @ButtonBorderWidth dashed @AccentMedium;
}
.commandContent {
padding: @DefaultSpace @DefaultSpace @DefaultSpace;
flex: 0 0 auto;
.commandIcon {
margin: 0 @SmallSpace 0 0;
vertical-align: text-top;
width: @ButtonIconSize;
height: @ButtonIconSize;
}
.commandLabel {
padding: 0px;
}
}
.commandContent .hasHiddenItems {
padding-right: @SmallSpace;
}
}
.commandButtonComponent.commandDisabled {
color: @BaseMediumHigh;
opacity: 0.5;
}
.commandExpand {
padding-top: @SmallSpace;
padding-bottom: @SmallSpace;
&:hover {
.hover();
& > .commandDropdownContainer {
display: block !important; // TODO: Remove after reusing KO mouseover and mouseout event handlers
}
}
&:focus {
.focus();
}
.commandDropdownLauncher {
cursor: pointer;
display: inline-flex;
.commandButtonComponent {
padding: 0px;
}
}
.expandDropdown {
padding: @SmallSpace;
img {
vertical-align: top;
}
}
.partialSplitter {
margin: @SmallSpace 0px 6px;
}
}
.commandButtonComponent[tabindex]:focus {
outline: none;
}
.selectedButton {
background-color: @AccentLow;
outline: none
}
}
.partialSplitter {
border-left: @ButtonBorderWidth solid @BaseMediumHigh;
}
.commandDropdown .commandButtonComponent {
padding-left: 0px;
}

View File

@@ -1,139 +0,0 @@
import * as ko from "knockout";
import { CommandButtonComponent, CommandButtonOptions } from "./CommandButton";
const mockLabel = "Some Label";
const id = "Some id";
function buildComponent(buttonOptions: any) {
document.body.innerHTML = CommandButtonComponent.template as any;
const vm = new CommandButtonComponent.viewModel(buttonOptions);
ko.applyBindings(vm);
}
describe("Command Button Component", () => {
function buildButtonOptions(
onClick: () => void,
id?: string,
label?: string,
disabled?: ko.Observable<boolean>,
visible?: ko.Observable<boolean>,
tooltipText?: string
): { buttonProps: CommandButtonOptions } {
return {
buttonProps: {
iconSrc: "images/AddCollection.svg",
id: id,
commandButtonLabel: label || mockLabel,
disabled: disabled,
visible: visible,
tooltipText: tooltipText,
hasPopup: false,
onCommandClick: onClick
}
};
}
function buildSplitterButtonOptions(
onClick: () => void,
id?: string,
label?: string,
disabled?: ko.Observable<boolean>,
visible?: ko.Observable<boolean>,
tooltipText?: string
): { buttonProps: CommandButtonOptions } {
const child: CommandButtonOptions = {
iconSrc: "images/settings_15x15.svg",
id: id,
commandButtonLabel: label || mockLabel,
disabled: disabled,
visible: visible,
tooltipText: tooltipText,
hasPopup: false,
onCommandClick: onClick
};
return {
buttonProps: {
iconSrc: "images/AddCollection.svg",
id: id,
commandButtonLabel: label || mockLabel,
disabled: disabled,
visible: visible,
tooltipText: tooltipText,
hasPopup: false,
onCommandClick: onClick,
children: [child]
}
};
}
afterEach(() => {
ko.cleanNode(document);
document.body.innerHTML = "";
});
describe("Rendering", () => {
it("should display button label", () => {
const buttonOptions = buildButtonOptions(() => {
/** do nothing **/
}, mockLabel);
buildComponent(buttonOptions);
expect(document.getElementsByClassName("commandButtonComponent").item(0).textContent).toContain(mockLabel);
});
it("should display button icon", () => {
const buttonOptions = buildButtonOptions(() => {
/** do nothing **/
});
buildComponent(buttonOptions);
expect(
document
.getElementsByTagName("img")
.item(0)
.getAttribute("src")
).toBeDefined();
});
});
describe("Behavior", () => {
let clickSpy: jasmine.Spy;
beforeEach(() => {
clickSpy = jasmine.createSpy("Command button click spy");
});
it("should trigger the click handler when the command button is clicked", () => {
const buttonOptions = buildButtonOptions(() => clickSpy());
buildComponent(buttonOptions);
document
.getElementsByClassName("commandButtonComponent")
.item(0)
.dispatchEvent(new Event("click"));
expect(clickSpy).toHaveBeenCalled();
});
it("should not trigger the click handler when command button is disabled", () => {
const buttonOptions = buildButtonOptions(() => clickSpy(), id, mockLabel, ko.observable(true));
buildComponent(buttonOptions);
document
.getElementsByClassName("commandButtonComponent")
.item(0)
.dispatchEvent(new Event("click"));
expect(clickSpy).not.toHaveBeenCalled();
});
it("should not have a dropdown if it has no child", () => {
const buttonOptions = buildButtonOptions(() => clickSpy(), id, mockLabel, ko.observable(true));
buildComponent(buttonOptions);
const dropdownSize = document.getElementsByClassName("commandExpand").length;
expect(dropdownSize).toBe(0);
});
it("should have a dropdown if it has a child", () => {
const buttonOptions = buildSplitterButtonOptions(() => clickSpy(), id, mockLabel, ko.observable(true));
buildComponent(buttonOptions);
const dropdownSize = document.getElementsByClassName("commandExpand").length;
expect(dropdownSize).toBe(1);
});
});
});

View File

@@ -1,191 +0,0 @@
/**
* How to use this component:
*
* In your html markup, use:
* <command-button params="{
* iconSrc: '/icon/example/src/',
* onCommandClick: () => { doSomething },
* commandButtonLabel: 'Some Label'
* disabled: true/false
* }">
* </command-button>
*
*/
import * as ko from "knockout";
import * as ViewModels from "../../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
import { KeyCodes } from "../../../Common/Constants";
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import template from "./command-button.html";
/**
* Options for this component
*/
export interface CommandButtonOptions {
/**
* image source for the button icon
*/
iconSrc: string;
/**
* Id for the button icon
*/
id: string;
/**
* Click handler for command button click
*/
onCommandClick: () => void;
/**
* Label for the button
*/
commandButtonLabel: string | ko.Observable<string>;
/**
* True if this button opens a tab or pane, false otherwise.
*/
hasPopup: boolean;
/**
* Enabled/disabled state of command button
*/
disabled?: ko.Subscribable<boolean>;
/**
* Visibility/Invisibility of the button
*/
visible?: ko.Subscribable<boolean>;
/**
* Whether or not the button should have the 'selectedButton' styling
*/
isSelected?: ko.Observable<boolean>;
/**
* Text to displayed in the tooltip on hover
*/
tooltipText?: string | ko.Observable<string>;
/**
* Callback triggered when the template is bound to the component
*/
onTemplateReady?: () => void;
/**
* tabindex for the command button
*/
tabIndex?: ko.Observable<number>;
/**
* Childrens command buttons to hide in the dropdown
*/
children?: CommandButtonOptions[];
}
export class CommandButtonViewModel extends WaitsForTemplateViewModel implements ViewModels.CommandButton {
public commandClickCallback: () => void;
public commandButtonId: string;
public disabled: ko.Subscribable<boolean>;
public visible: ko.Subscribable<boolean>;
public isSelected: ko.Observable<boolean>;
public iconSrc: string;
public commandButtonLabel: ko.Observable<string>;
public tooltipText: ko.Observable<string>;
public tabIndex: ko.Observable<number>;
public isTemplateReady: ko.Observable<boolean>;
public hasPopup: boolean;
public children: ko.ObservableArray<CommandButtonOptions>;
public constructor(options: { buttonProps: CommandButtonOptions }) {
super();
const props = options.buttonProps;
const commandButtonLabel = props.commandButtonLabel;
const tooltipText = props.tooltipText;
this.commandButtonLabel =
typeof commandButtonLabel === "string" ? ko.observable<string>(commandButtonLabel) : commandButtonLabel;
this.commandButtonId = props.id;
this.disabled = props.disabled || ko.observable(false);
this.visible = props.visible || ko.observable(true);
this.isSelected = props.isSelected || ko.observable(false);
this.iconSrc = props.iconSrc;
this.tabIndex = props.tabIndex || ko.observable(0);
this.hasPopup = props.hasPopup;
this.children = ko.observableArray(props.children);
super.onTemplateReady((isTemplateReady: boolean) => {
if (isTemplateReady && props.onTemplateReady) {
props.onTemplateReady();
}
});
if (tooltipText && typeof tooltipText === "string") {
this.tooltipText = ko.observable<string>(tooltipText);
} else if (tooltipText && typeof tooltipText === "function") {
this.tooltipText = tooltipText;
} else {
this.tooltipText = this.commandButtonLabel;
}
this.commandClickCallback = () => {
if (this.disabled()) {
return;
}
const el = document.querySelector(".commandDropdownContainer") as HTMLElement;
if (el) {
el.style.display = "none";
}
props.onCommandClick();
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
commandButtonClicked: this.commandButtonLabel
});
};
}
public onKeyPress(source: any, event: KeyboardEvent): boolean {
if (event.keyCode === KeyCodes.Space || event.keyCode === KeyCodes.Enter) {
this.commandClickCallback && this.commandClickCallback();
event.stopPropagation();
return false;
}
return true;
}
public onLauncherKeyDown(source: any, event: KeyboardEvent): boolean {
// TODO: Convert JQuery code into Knockout
if (event.keyCode === KeyCodes.DownArrow) {
$(event.target)
.parent()
.siblings()
.children(".commandExpand")
.children(".commandDropdownContainer")
.hide();
$(event.target)
.children(".commandDropdownContainer")
.show()
.focus();
event.stopPropagation();
return false;
}
if (event.keyCode === KeyCodes.UpArrow) {
$(event.target)
.children(".commandDropdownContainer")
.hide();
event.stopPropagation();
return false;
}
return true;
}
}
/**
* Helper class for ko component registration
*/
export const CommandButtonComponent = {
viewModel: CommandButtonViewModel,
template
};

View File

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

View File

@@ -1,40 +0,0 @@
<span
class="commandButtonComponent"
role="menuitem"
tabindex="0"
data-bind="setTemplateReady: true,
css: {
commandDisabled: disabled,
selectedButton: isSelected
},
event: {
keypress: onKeyPress
},
attr: {
title: tooltipText,
id: commandButtonId,
tabindex: tabIndex ,
'aria-disabled': disabled,
'aria-haspopup': hasPopup
},
click: commandClickCallback,
visible: visible"
>
<div class="commandContent" data-bind="css: { hasHiddenItems: children().length > 0 }">
<img class="commandIcon" data-bind="attr: {src: iconSrc, alt: commandButtonLabel}" />
<span class="commandLabel" data-bind="text: commandButtonLabel"></span>
</div>
</span>
<!-- ko if: children().length > 0 -->
<div class="commandExpand" tabindex="0" data-bind="visible: visible, event: { keydown: onLauncherKeyDown }">
<div class="commandDropdownLauncher">
<span class="partialSplitter"></span>
<span class="expandDropdown"> <img src="/QueryBuilder/CollapseChevronDown_16x.png" /> </span>
</div>
<div class="commandDropdownContainer">
<div class="commandDropdown" data-bind="foreach: children">
<command-button params="{buttonProps: $data}"></command-button>
</div>
</div>
</div>
<!-- /ko -->

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ const createBlankProps = (): DefaultDirectoryDropdownProps => {
return {
defaultDirectoryId: "",
directories: [],
onDefaultDirectoryChange: jest.fn()
onDefaultDirectoryChange: jest.fn(),
};
};
@@ -17,7 +17,7 @@ const createBlankDirectory = (): Tenant => {
displayName: "",
domains: [],
id: "",
tenantId: ""
tenantId: "",
};
};
@@ -90,27 +90,15 @@ describe("test function", () => {
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);
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).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);
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).toHaveBeenCalled();
});

View File

@@ -2,6 +2,7 @@
* React component for Switch Directory
*/
import _ from "underscore";
import * as React from "react";
import { Dropdown, IDropdownOption, IDropdownProps } from "office-ui-fabric-react/lib/Dropdown";
import { Tenant } from "../../../Contracts/DataModels";
@@ -18,13 +19,13 @@ export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDi
public render(): JSX.Element {
const lastVisitedOption: IDropdownOption = {
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(
(dirc): IDropdownOption => {
return {
key: dirc.tenantId,
text: `${dirc.displayName}(${dirc.tenantId})`
text: `${dirc.displayName}(${dirc.tenantId})`,
};
}
);
@@ -34,7 +35,7 @@ export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDi
options: dropDownOptions,
defaultSelectedKey: this.props.defaultDirectoryId ? this.props.defaultDirectoryId : lastVisitedOption.key,
onChange: this._onDropdownChange,
className: "defaultDirectoryDropdown"
className: "defaultDirectoryDropdown",
};
return <Dropdown {...dropDownProps} />;
@@ -55,12 +56,12 @@ export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDi
countryCode: undefined,
displayName: undefined,
domains: [],
id: undefined
id: undefined,
});
return;
}
const selectedDirectory = this.props.directories.find(d => d.tenantId === option.key);
const selectedDirectory = _.find(this.props.directories, (d) => d.tenantId === option.key);
if (!selectedDirectory) {
return;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
<div class="warningErrorContainer" data-bind="visible: !!params.errorMsg()">
<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>
</div>
</div>

View File

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

View File

@@ -0,0 +1,11 @@
import React from "react";
import { shallow } from "enzyme";
import { FeaturePanelComponent } from "./FeaturePanelComponent";
describe("Feature panel", () => {
it("renders all flags", () => {
const wrapper = shallow(<FeaturePanelComponent />);
expect(wrapper).toMatchSnapshot();
});
});

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